当前位置:嗨网首页>书籍在线阅读

15-移动构造函数解析

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

18.2.3 移动构造函数解析

虽然使用右值引用可支持移动语义,但这并不会神奇地发生。要让移动语义发生,需要两个步骤。首先,右值引用让编译器知道何时可使用移动语义:

Useless two = one;          // matches Useless::Useless(const Useless &)
Useless four (one + three); // matches Useless::Useless(Useless &&)

对象one是左值,与左值引用匹配,而表达式one + three是右值,与右值引用匹配。因此,右值引用让编译器使用移动构造函数来初始化对象four。实现移动语义的第二步是,编写移动构造函数,使其提供所需的行为。

总之,通过提供一个使用左值引用的构造函数和一个使用右值引用的构造函数,将初始化分成了两组。使用左值对象初始化对象时,将使用复制构造函数,而使用右值对象初始化对象时,将使用移动构造函数。程序员可根据需要赋予这些构造函数不同的行为。

这就带来了一个问题:在引入右值引用前,情况是什么样的呢?如果没有移动构造函数,且编译器未能通过优化消除对复制构造函数的需求,结果将如何呢?在C++98中,下面的语句将调用复制构造函数:

Useless four (one + three);

但左值引用不能指向右值。结果将如何呢?第8章介绍过,如果实参为右值,const引用形参将指向一个临时变量:

int twice(const & rx) {return 2 * rx;}
...
int main()
{
    int m = 6;
    // below, rx refers to m
    int n = twice(m);
    // below, rx refers to a temporary variable initialized to 21
    int k = twice(21);
...

就Useless而言,形参f将被初始化一个临时对象,而该临时对象被初始化为operator+()返回的值。下面是使用老式编译器进行编译时,程序清单18.2所示程序(删除了移动构造函数)的部分输出:

...
Entering operator+()
int constructor called; number of objects: 4
Number of elements: 30 Data address: 01C337C4
temp object:
Leaving operator+()
copy const called; number of objects: 5
Number of elements: 30 Data address: 01C337E8
destructor called; objects left: 4
deleted object:
Number of elements: 30 Data address: 01C337C4
copy const called; number of objects: 5
Number of elements: 30 Data address: 01C337C4
destructor called; objects left: 4
deleted object:
Number of elements: 30 Data address: 01C337E8
...

首先,在方法Useless::operator+() 内,调用构造函数创建了temp,并在01C337C4处给它分配了存储30个元素的空间。然后,调用复制构造函数创建了一个临时复制信息(其地址为01C337E8),f指向该副本。接下来,删除了地址为01C337C4的对象temp。然后,新建了对象four,它使用了01C337C4处刚释放的内存。接下来,删除了01C337E8处的临时参数对象。这表明,总共创建了三个对象,但其中的两个被删除。这些就是移动语义旨在消除的额外工作。

正如g++示例表明的,机智的编译器可能自动消除额外的复制工作,但通过使用右值引用,程序员可指出何时该使用移动语义。