C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(8)
14、编译器生成拷贝构造函数与拷贝赋值操作符
如果类有用户声明的拷贝赋值操作符或者析构函数,c++11使拷贝构造函数的生成失效。如果在这种情况下你仍然需要一个编译器生成的拷贝构造函数,可以显式地缺省一个:
MyClass(const MyClass& src) = default;
如果类有用户声明的拷贝构造函数或者析构函数,c++11也使拷贝赋值操作符的生成失效。如果在这种情况下你仍然需要一个编译器生成拷贝赋值操作符,可以显式地缺省一个:
MyClass& operator=(const MyClass& rhs) = default;
15、区分拷贝与赋值
有时候很难区分什么时候对象用拷贝构造函数初始化,而不用赋值操作符赋值。其实质是,如果看起来像是一个声明,就要用拷贝构造函数,如果看起来像赋值语句,就要用赋值操作符来处理。考虑下面的代码:
SpreadsheetCell myCell { 5 };
SpreadsheetCell anotherCell { myCell };
anotherCell使用拷贝构造函数来构建。现在考虑下面的代码:
SpreadsheetCell aThirdCell = myCell;
aThirdCell也使用拷贝构造函数来构建,因为这是一个声明。operator=在这行代码中不会被调用!这个语法是SpreadsheetCell{myCell};的另一种写法。然而,考虑下面的代码:
anotherCell = myCell; // Calls operator= for anotherCell
这里,anotherCell早已构建,所以编译器会调用operator=。
15.1、作为返回值的对象
当从函数返回对象时,有时候很难看出到底是拷贝还是赋值。例如,SpreadsheetCell::getString()的实现看起来像这样:
string SpreadsheetCell::getString() const
{return doubleToString(m_value);
}
现在,考虑如下代码:
SpreadsheetCell myCell2 { 5 };
string s1;
s1 = myCell2.getString();
当getString()返回string,编译器实际上是通过调用string拷贝构造函数来生成一个没有命名的临时的string对象。当把结果赋值给s1时,赋值操作符被调用,使用临时的string作为参数。这时,临时string对象就被析构掉了。这样的话,这一行代码调用了拷贝构造函数与赋值操作符(作用于不同的对象)。
如果你还没有迷糊的话,考虑下面的代码:
SpreadsheetCell myCell3 { 5 };
string s2 = myCell3.getString();
在这种情况下,getString()仍然在返回时生成临时的未命名的string对象。但是,现在s2使得拷贝构造函数被调用,而不是赋值操作符。
使用move语法,编译器可以使用move构造函数或者move赋值操作符而不是拷贝构造函数或者拷贝赋值操作符来从getString()返回string。在特定情况下更高效。然而,更好的是,编译器可以自由地(通常也这样要求)实现拷贝省音来优化掉大量的拷贝操作或者移动操作,当返回值的时候。
如果你忘记了这些事情发生的顺序或者哪个构造函数或者操作符被调用,可以通过临时包含有帮助的输出在代码中或者通过调试器一步步地查看代码来弄明白。
15.2、拷贝构造函数与对象成员
也应该注意到赋值操作符与构造函数中的拷贝构造函数之间的区别。如果一个对象包含另外的对象,编译器生成的拷贝构造函数会递归地调用第一个包含对象的拷贝构造函数。当写自己的拷贝构造函数时,可以通过使用构造函数初始化器来提供同样的语法,如前所示。如果在构造函数初始化器中省略了一个数据成员,编译器就会对其执行缺省初始化(调用对象的缺省构造函数),在执行构造函数体的代码之前。这样的话,在构造函数体执行时,对象的所有数据成员就已经初始化了。
例如,可以像这样写SpreadsheetCell的拷贝构造函数:
SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src)
{m_value = src.m_value;
}
然而,当给拷贝构造函数体的数据成员赋值时,使用的是赋值操作符,而不是拷贝构造函数,因为它们已经初始化过了。
如果像下面这样写拷贝构造函数,m_value使用拷贝构造函数初始化:
SpreadsheetCell::SpreadsheetCell(const SpreadsheetCell& src)
: m_value { src.m_value }
{
}