四十一、auto不能用于分配数组
虽然我们用空括号对数组中的元素进行值初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组。
四十二、动态分配数组的列表初始化
在新标准中,我们可以提供一个元素初始化器的花括号列表:
//10个int分别用列表中对应的初始化器初始化
int *pia = new int[10]{0,1,2,3,4,5,6,7,8,9};
//10个string,前4个用给定的初始化器初始化,剩余的进行值初始化
string *pia3 = new string[10]{"a", "an", "the", string(3,'x')};
与内置数组对象的列表初始化一样,初始化器会用来初始化动态数组中开始部分的元素。如果初始化器数目小于元素数目,剩余元素将进行值初始化。如果初始化器数目大于元素数目,则new表达式失败,不会分配任何内存。
四十三、allocator分配未构造的内存
allocator分配的内存是未构造的(unconstructed)。我们按需要在此内存中构造对象。在新标准中,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象。类似make_shared的参数,这些额外参数必须是与构造的对象的类型相匹配的回合肥的初始化器:
auto q = p; //q指向最后构造的元素之后的位置
alloc.construct(q++); //*q为空字符串
alloc.consturct(q++, 10, 'c'); //*q为cccccccc
alloc.construct(q++, "hi"); //*q为hi
在早期版本的标准库中,construct只接受两个参数:指向创建对象位置的指针和一个元素类型的值。因此,我们只能将一个元素拷贝到未构造空间中,而不能用元素类型的任何其他构造函数来构造一个元素。
还未构造对象的情况下就使用原始内存是错误的:
cout<<*p<<endl; //正确:使用string的输出字符串
cout<<*q<<endl; //灾难:q指向未构造的内存
四十四、将=default用于拷贝控制成员
我们可以通过拷贝类控制成员定义为=default来显式地要求编译器生成合成的版本
class Sales_data{
public:
//拷贝控制成员;使用default
Sales_data() = default;
Sales_data(const Sales_data&) = default;
Sales_data& operator=(const Sales_data &);
~Sales_data() = default;
//其他成员的定义,如前
};
Sales_data& Sales_data::operator=(const Sales_data&) = default;
当我们在类内使用=default修饰成员的声明时,合成的函数将隐式地声明为内联的。
四十五、将=default阻止拷贝类对象
在新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝。删除的函数是这样一种函数:我们虽然声明了它们,但不能以任何方式使用它们。
在函数的参数后面加上=delete来指出我们希望将它定义为删除的:
struct NoCopy{
NoCopy() = default; //使用合成的默认构造函数
NoCopy(const NoCopy) = delete; //阻止拷贝
NoCopy &operator=(const NoCopy&) = delete; //阻止赋值
~NoCopy() = default; //使用合成的析构函数
//其他成员
};
=delete通知编译器,我们不希望定义这些成员。
四十六、用移动类对象替代拷贝类对象
通过使用新标准引入的两种机制,我们就可以避免string的拷贝。首先,有一些标准库类,包括string,都定义了所谓的“移动构造函数”
四十七、右值引用
为了支持移动操作,新标准引入了一种新的引用类型-右值引用(rvalue reference)。所谓右值引用就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。如我们将要看到的,右值引用有一个重要的性质--只能绑定到一个将要销毁的对象。因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。
一般而言,一个左值表达式表达的是一个对象的身份,一个右值表达式表达的是对象的值。
对于常规引用,我们不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。右值引用有着完全相反的绑定特性:我们可以将一个右值引用绑定到这类表达式中,但不能将这些右值引用直接绑定到一个左值上:
int i = 42;
int &r = i; //正确,r引用i
int &&rr = i; //错误,不能将一个右值引用绑定到左值上
int &r = i * 42; //错误,i*42是一个右值
const int &r3 = i * 42; //正确,我们可以将一个const的引用绑定到一个右值上
int &&rr2 = i * 42; //正确,将rr2绑定到乘法结果上
四十八、标准库move函数
虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值显式地转换为对应的右值引用类型。我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用,此函数定义在utility中。
int &&rr1 = 42; //正确:字面常量是右值
int &&rr2 = rr1; //错误:表达式rr1是左值
int &&rr3 = std::move(rr1); //ok
四十九、移动构造函数和移动赋值
类似拷贝构造函数,移动构造函数的第一个参数是该类类型的一个引用。不同于拷贝构造函数的是,这个参数在移动构造函数中是一个右值引用。与拷贝构造函数一样,任何额外的参数都必须有默认的实参。
五十、虚函数的override指示符
派生类经常(但不总是)覆盖它继承的虚函数。如果派生类没有覆盖其基类中的某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。
派生类可以在它覆盖的函数前使用virtual关键字,但不是非得这么做。c++11新标准允许派生类显式地注明它使用某个成员函数覆盖了它继承的虚函数。具体做法是在形参列表
后面、或者在const成员函数的const关键字后面、或者在引用成员函数的引用限定符后面添加一个关键字override。