原文地址:http://www.cplusplus.com/articles/jsmith1/
复制构造是类或结构体的一种特殊的拷贝已存在实例构造函数。根据C++标准,复制构造函数可以有以下几种形式:
MyClass( const MyClass& other ); MyClass( MyClass& other ); MyClass( volatile const MyClass& other ); MyClass( volatile MyClass& other );
注意不要写成以下形式,尽管它们也能够实现同样的事:
MyClass( MyClass* other );
MyClass( const MyClass* other );
MyClass( MyClass other );
首先,你需要明白假如你没声明一个复制构造函数,编译器会给你提供默认的构造函数。这个默认的构造函数能智能地(member-wise)复制源对象,比如这个类:
class MyClass { int x; char c; std::string s; };
编译器提供的默认构造函数相当以下功能:
MyClass::MyClass( const MyClass& other ) :
x( other.x ), c( other.c ), s( other.s )
{}
在很多情况下,这是足够的。然而,有些情景下member-wise的拷贝方式是不够的。尤为常见的原因是默认的复制构造会产生野指针(raw pointer),这个时候你需要深复制指针成员,这是当你不是想复制指针本身,而是想复制指针指向的内容。为什么需要深复制?这是因为通常一个实例拥有指针,同时在某些时候它也负责删除这个指针,比如析构函数发生的前一刻。假如两个对象都析构调用 delete 函数删除非空指针成员,会导致堆栈溢出。
使用编译器提供的默认复制构造函数,很少情况不会出现野指针,因此默认构造函数是不足够的。你也可以使用引用计数。比如boost::shared_ptr<>。
当通过引用的方式传递参数给函数或者构造对象时,要很小心地正确使用const。仅仅当函数会修改传递的函数才使用non-canst引用,否则就应该使用const引用。
为什么强调要这样? 这是一个C++标注的小条款:non-const 引用不能绑定到临时对象,临时对象时一个没有变量名的实例,比如:
std::string( "Hello world" );
是一个临时对象,因为没有变量名。下面一个不是临时对象:
std::string s( "Hello world" );
这个实用的提醒有什么用?请看下面:
// Improperly declared function: parameter should be const reference: void print_me_bad( std::string& s ) { std::cout << s << std::endl; } // Properly declared function: function has no intent to modify s: void print_me_good( const std::string& s ) { std::cout << s << std::endl; } std::string hello( "Hello" ); print_me_bad( hello ); // Compiles ok; hello is not a temporary print_me_bad( std::string( "World" ) ); // Compile error; temporary object print_me_bad( "!" ); // Compile error; compiler wants to construct temporary // std::string from const char* print_me_good( hello ); // Compiles ok print_me_good( std::string( "World" ) ); // Compiles ok print_me_good( "!" ); // Compiles ok
很多STL的容器和算法都要求一个对象时可拷贝的。通常,这意味着你需要有实用const传引用的复制构造函数。 (最近一个搞了很久的错误就是这个问题,所以觉得这篇文章很好,就翻译过来分享了)
插曲(译者经历):
我在使用list的时候,用到了replace泛型算法:
template < class ForwardIterator, class T > void replace ( ForwardIterator first, ForwardIterator last, const T& old_value, const T& new_value ) { for (; first != last; ++first) if (*first == old_value) *first=new_value; }
很明显这个算法要求对象具有 == 操作符来比较。我也为对象重载了 == 操作符:
bool operator==(Person& lhs,Person& rhs) { if (wcscmp(lhs.name,rhs.name)==0) { return true; } else return false; }
我这样使用算法:
replace(DataList.begin(),DataList.end(),temp1,temp);
//list<Person> DataList; Person temp,temp1;
尽管我都重载了 == 操作符,但编译器还是报出:
二进制“==”: 没有找到接受“Person”类型的左操作数的运算符(或没有可接受的转换)
这样的错误。
加上const后就可以了 ,也就是:
bool operator==(const Person& lhs,const Person& rhs)
问题原因关键在于:replace算法的参数是使用了const引用,而当客户自定义的 == 操作符是non-const引用,就会出现一个没有接受的转换,要知道,const数据类型是不能转换到non-const类型的,相反non-const就可以转换到const类型,正确使用const是很重要的!
赋值操作符是允许你使用= 给一个实例赋值,比如:
MyClass c1, c2;
c1 = c2; // assigns c2 to c1
赋值操作符有以下几种不同形式:
(1) MyClass& operator=( const MyClass& rhs ); (2) MyClass& operator=( MyClass& rhs ); (3) MyClass& operator=( MyClass rhs ); (4) const MyClass& operator=( const MyClass& rhs ); (5) const MyClass& operator=( MyClass& rhs ); (6) const MyClass& operator=( MyClass rhs ); (7) MyClass operator=( const MyClass& rhs ); (8) MyClass operator=( MyClass& rhs ); (9) MyClass operator=( MyClass rhs );
返回值的类型相对不重要(但译者我觉得还是跟系统内置的=返回值一样较为合适,也就是返回对象的引用)
(2)(5)(8)通过non-caonst传递右值引用,当出现以下情况,就会编译出错:
MyClass c1; c1 = MyClass( 5, 'a', "Hello World" ); // assuming this constructor exists
首先,你要明白这个跟复制构造函数一样,假如你没声明赋值操作符,编译器同样为你提供一个默认的,同样这个默认的赋值操作符也只是“浅赋值” (member-wise assignment).跟以下功能相当:
MyClass& MyClass::operator=( const MyClass& rhs ) { x = other.x; c = other.c; s = other.s; return *this; }
同样也是会产生也指针的问题。通常任何时候你需要写自定义的复制构造函数,也就需要些自定义的赋值操作符。
本文固定链接: http://www.coderess.com/?p=55 | Pro.Charm