8 模板和STL方面
为了改善模板和标准化方面的易用性,C++ 11做了多个改进:
8.1 改进的for循环
double prices[5] = {1, 2, 3, 4, 5};
for(double x : prices)
for(auto x : prices)
如果要在循环中修改数组或容器中的每个元素,可以使用引用:
for(auto& x : prices)
8.2 新增的STL容器
C++ 11新增了forward_list、unordered_map、unordered_multimap、unordered_set、unordered_multiset。
新增了模板array,实例化时指定元素类型和个数:
std::array<int, 10> ar; // 10个int
8.3 新增STL方法
cbegin()、cend()、crbegin()、crend()。这几个方法将容器元素视为const。
8.4 valarray升级版
对于valarray模板,C++ 11新增了两个方法begin()和end()。
8.5 摒弃了export
C++ 98增加的关键字export可以让程序员将模板定义放在接口文件中,实现文件中放置方法体,但是实践证明这一点也不现实,因此C++ 11把它摒弃了。
8.6 尖括号
旧时的C++要求在定义嵌套的模板时,两个尖括号之间必须有空格,比如:
vector<list<int> >
,但是C++ 11中不再需要这样了,比如vector<list<int>>
也可以通过的。
9 右值引用
C++新增了右值引用,使用&&表示。【相对于左值,右值表示字面常量、表达式、函数的非引用返回值等】
如:
int&& r1 = 12;
int x = 5;
int y = 8;
int&& r2 = x + y;
我们可以通过r1来修改12,很方便。
10 移动语义和右值引用
10.1 为何需要移动语义
移动语义就是为了避免多余的复制工作,就是说与其复制,还不如将源地址传给使用者,因为有时复制工作确是没什么用。
要实现移动语义,需要采用某种措施让编译器知道到底什么情况下需要复制,什么情况下不必复制。这时,右值引用就可以配上用场了。
传统的复制构造函数执行深复制,并且不改变实参,因此,参数为const类型;
移动构造函数只是更改了引用记录,并且可能会改变实参,因此,参数必须是非const类型;
如下实例:
class Use
{
public:
Use();
Use(const Use& f); // copy constructor
Use(Use&& f); // move constructor
...
private:
int n;
char* pc;
};
...
Use::Use(const Use& f) : n(f.n) // 深复制
{
pc = new char[n];
for(int i = 0; i < n; i++)
pc[i] = f.pc[i];
}
Use::Use(Use&& f) : n(f.n)
{
pc = f.pc; // 移动构造,转移控制权
f.pc = nullptr;
f.n = 0;
}
10.2 移动构造解析
虽然移动构造定义好了,但是如何调用呢,也就是说什么情况下才会发生移动构造呢?
必须使用右值引用调用:
Use two = one; // match Use::Use(const Use&);
Use four(one + three); // match Use::Use(Use&&);
因为one是左值,而one + three是右值。
移动赋值情况类似,不记录了。
10.3 强制移动
移动构造和移动赋值使用右值引用,如果需要让他们操作左值呢?
答案是使用static_cast<>将对象强制转换为Use&&即可。不过在C++ 11种提供了更便捷的操作,在头文件utility中声明的函数std::move(…)。
如果使用了std::move()函数调用,但是类中却没有定义移动相关函数,那么编译器会调用传统版本的移动构造函数和移动赋值操作符。
11 新的类功能
假设你为类定义了构造函数,那么类就不会自动提供默认的构造函数了,然而,如果你仍然想使用类提供的默认版本,那么可以使用default关键字:
class Some
{
public:
Some(Some&&);
Some() = default; // use default constructor
...
};
相反地,如果要禁用编译器提供的默认函数,可以使用delete:
class Some
{
public:
Some(Some&&);
Some() = default; // use default constructor
Some(const Some&) = delete; //disable copy constructor
...
};
当然要想禁用某个编译器提供的函数也可以显式声明为private,但是使用delete更方便且不易出错。
注意:default关键字只能用于6个特殊函数,而delete却能够用于任何成员函数。
委托构造
如果一个类包含多个构造函数,C++ 11允许在一个构造函数中的定义中使用另一个构造函数,但这必须通过初始化列表进行操作,如下:
class Notes
{
int k; double x; string s;
public:
Notes(int kk, double xx, string ss) : k(kk), x(xx), s(ss){ }
Notes(int kk) : Notes(0, 0.5, “benxin”){ k = kk; }
...
};
继承构造
C++ 11允许派生类继承基类的构造函数。C++ 98提供了一种让某个名称空间中的同名重载函数都可用的语法,如下:
namespace Box
{
int fn(){}
int fn(int){}
int fn(double){}
}
using Box::fn;该语句使得fn的所有重载版本都可用。我们一般使用这种方法在派生类中调用基类的同名函数。之所以提供这种语法,就是因为覆盖是以函数名为基础的,不论参数是否对应都将被覆盖。
C++ 11将这种语法用于构造函数中,使得派生类将继承基类的构造函数(默认构造函数、复制构造函数、移动构造函数除外)。