【读书笔记:C++ primer plus 第六版 中文版】第18章 探讨C++新标

转载请注明出处:

本章首先复习前面介绍过的C++11功能,然后介绍如下主题:

18.1 复习前面介绍过的C++11功能 18.1.1 新类型 18.1.2 统一的初始化

C++11扩大了用大括号括起的列表(初始化列表)的适用范围,使其可用于所有内置类型和用户定义的类型(即类对象).使用初始化列表时,可添加登好(=),也可不添加.1.缩窄 初始化列表语法可防止缩窄,即禁止将数值赋给无法存储它的数值变量.2.std::initializer_list C++11提供了模板类std::initializer_list,可将其用作构造函数的参数.

18.1.3 声明

C++11提供了多种简化声明的功能,尤其在使用模板时.1.auto 2.decltype 关键字decltype将变量的类型声明为表达式指定的类型.decltype的工作原理比auto复杂,根据使用的表达式,指定的类型可以为引用和const.3.返回类型后置 新增的一种函数声明语法:在函数名和参数列表后面(而不是前面)指定返回类型;4.模板别名:using = typedef和using=的差别在于,新语法也可用于模板部分具体化,单typedef不能;5.nullptr

18.1.4 智能指针

基于程序员的编程体验和BOOST库提供的解决方案,C++11摒弃了auto_ptr,并新增了三种智能指针:unique_ptr,shared_ptr和weak_ptr.所有新增的智能指针都能与STL容器和移动语义协同工作.

18.1.5 异常规范方面的修改

与auto_ptr一样,C++编程社区的集体经验表明,异常规范的效果没有预期的好.因此,C++11摒弃了异常规范.然而,标准委员会认为,指出函数不会引发异常有一定的价值,他们为此添加了关键字noexcept;

18.1.6 作用域内枚举

传统的C++枚举提供了一种创建名称常量的方式,但其类型检查相当低级.另外,枚举名的作用域为枚举定义所属的作用域,这意味着如果在同一个作用域内定义两个枚举,他们的枚举成员不能同名.最后,枚举可能不是可完全移植的,因为不同的实现可能选择不同的底层类型.为解决以上问题,C++11新增了一种枚举,这种枚举使用class或struct定义;

18.1.7 对类的修改

1.显式转换运算符 C++引入了关键字explicit,以禁止单参数构造函数导致的自动转换.C++11扩展了explicit的这种用法,使得可对转换函数做类似的处理.2.类内成员初始化 这样做,可避免在构造函数中编写重复的代码,从而降低了程序员的工作量,厌倦情绪和出错的机会.如果构造函数在成员初始化列表中提供了相应的值,这些默认值将被覆盖.

18.1.8 模板和STL方面的修改

基于范围的for循环 如果要在循环中修改数组或容器的每个元素,可使用引用类型.新的STL容器 C++11新增了STL容器forward_list,unordered_map,unordered_multimap,unordered_set和unordered_multiset.C++11还新增了模板array.要实例化这种模板,可指定元素类型和固定的元素数.新的STL方法 C++11新增了STL方法cbegin()和cend().这些新方法将元素视为const.与此类似,crbegin()和crend()是rbegin()和rend()的const版本.valarray升级 模板valarray独立于STL开发的,其最初的设计导致无法将基于范围的STL算法用于valarray对象.C++11天假了两个函数(begin()和end()),它们都接收valarray作为参数,并返回迭代器,这些迭代器分别指向valarray对象的第一个元素和最后一个元素后面.这让你能够将基于范围的STL算法用于valarray.摒弃export 仍保留了关键字export,供以后使用尖括号 为避免与运算符>>混淆,C++要求在声明嵌套模板时使用空格将尖括号分开,C++11不再这样要求.

18.1.9 右值引用

18.2 移动语义和右值引用 18.2.1 为何需要移动语义

实际文件还留在原来的地方,而只修改记录.这种方法被称为移动语义.有点悖论的是,移动语义实际上避免了移动原始数据,而只是修改了记录.要实现移动语义,需要采取某种方式,让编译器知道什么时候需要复制,什么时候不需要.这就是右值引用发挥作用的地方.

18.2.2 一个移动示例

程序清单18.2 useless.cpp;// interfaceclass Useless{private:ct; // number of objectsvoid ShowObject() const;public:Useless();explicit Useless(int k);Useless(int k, char ch);Useless(const Useless & f); // regular copy constructorUseless(Useless && f);// move constructor~Useless();Useless operator+(const Useless & f)const;// need operator=() in copy and move versionsvoid ShowData() const;};// implementationint Useless::ct = 0;Useless::Useless(){++ct;n = 0;pc = nullptr;cout << “default constructor called; number of objects: ” << ct << endl;ShowObject();}Useless::Useless(int k) : n(k){++ct;cout << “int constructor called; number of objects: ” << ct << endl;pc = new char[n];ShowObject();}Useless::Useless(int k, char ch) : n(k){++ct;cout << “int, char constructor called; number of objects: ” << ct << endl;pc = new char[n];for (int i = 0; i < n; i++)pc[i] = ch;ShowObject();}Useless::Useless(const Useless & f): n(f.n) {++ct;cout << “copy const called; number of objects: ” << ct << endl;pc = new char[n];for (int i = 0; i < n; i++)pc[i] = f.pc[i];ShowObject();}Useless::Useless(Useless && f): n(f.n) {++ct;cout << “move constructor called; number of objects: ” << ct << endl;pc = f.pc;// steal addressf.pc = nullptr; // give old object nothing in returnf.n = 0;ShowObject();}Useless::~Useless(){cout << “destructor called; objects left: ” << –ct << endl;cout << “deleted object:\n”;ShowObject();delete [] pc;}Useless Useless::operator+(const Useless & f)const{cout << “Entering operator+()\n”;Useless temp = Useless(n + f.n);for (int i = 0; i < n; i++)temp.pc[i] = pc[i];for (int i = n; i < temp.n; i++)temp.pc[i] = f.pc[i – n];cout << “temp object:\n”;cout << “Leaving operator+()\n”;return temp;}void Useless::ShowObject() const{cout << “Number of elements: ” << n;cout << ” Data address: ” << (void *) pc << endl;}void Useless::ShowData() const{if (n == 0)cout << “(object empty)”;elsefor (int i = 0; i < n; i++)cout << pc[i];cout << endl;}// applicationint main(){{Useless one(10, ‘x’);Useless two = one;// calls copy constructorUseless three(20, ‘o’);Useless four(one + three); // calls operator+(), move constructorcout << “object one: “;one.ShowData();cout << “object two: “;two.ShowData();cout << “object three: “;three.ShowData();cout << “object four: “;four.ShowData();}// cin.get();}其中最重要的是复制构造函数和移动构造函数的定义.注意到没有调用移动构造函数,且只创建了4个对象.创建对象four时,该编译器没有调用任何构造函数;相反,它推断出对象four是operator+()所做工作的受益人,因此将operator+()创建的对象转到four的名下.一般而言,编译器完全可以进行优化,只要结构与未优化时相同.即使省略该程序中的移动构造函数,并使用g++进行编译,结构也将相同.

18.2.3 移动构造函数解析

虽然使用右值引用可支持移动语义,但这并不会神奇地发生.要让移动语义发生,需要两个步骤.

18.2.4 赋值

适用于构造函数的移动语义考虑也适用于赋值运算符.与移动构造函数一样,移动赋值运算符的参数也不能是const引用,因为这个方法修改了源对象.

18.2.5 强制移动

18.3 新的类功能 18.3.1 特殊的成员函数

在原有4个特殊成员函数(默认构造函数,赋值构造函数,复制赋值运算符和析构函数)的基础上,C++11新增了两个:移动构造函数和移动赋值运算符.

18.3.2 默认的方法和禁用的方法

18.3.3 委托构造函数

C++11允许在一个构造函数的定义中使用另一个构造函数.这被称为委托.有些人注定是等待别人的,有些人是注定被人等的。

【读书笔记:C++ primer plus 第六版 中文版】第18章 探讨C++新标

相关文章:

你感兴趣的文章:

标签云: