首页 > 代码库 > C++对象复制控制
C++对象复制控制
C++中对象的复制通过一系列的构造函数或者赋值函数来实现的,包括copy construstor,move construstor,copy assignment operator,move assignment operator,实现过程中还可能有destructor的参与,一般情况下,这些函数编译器会为我们自动合成,但还有很多时候,编译器会将他们合成为delete,因此需要我们自己编写。也有一些时候,我们需要禁止或者强制编译器为我们合成这些函数。
首先看一下什么情况下编译器不会为我们自动合成这些函数
这些函数都有一个共同的特性,即析构函数被标记为delete或者不可访问时,所有编译器自动合成的函数都会被标记为delete,因为编译器不自愿生成无法析构的对象。同时,如果他们的父类或者成员的对应函数被标记为delete或者不可访问时,编译器也不会自动合成,因为编译器自动合成的函数需要依赖这些函数实现(内置类型不遵循这些规则)
default construstor:
1)一个类提供了自定义的任何构造函数
<span style="font-size:18px;">class Demon { public: Demon(const string &s):id(s){} private: string id; }; Demon d; ///错误,无默认构造函数</span>
2)一个类拥有标记为delete或者无访问权限的析构函数的成员或父类,或者其本身析构函数被标记为delete或无权访问
class Demon { ~Demon()=delete; }; class DerivedDemon:public Demon { }; class WrapDemon { private: Demon d; }; ///下面三个对象创建都是错误的,因为 Demon d; DerivedDemon dd; WrapDemon wd;
3)一个类拥有一个引用类型的成员,但并未对其进行类内初始化
<span style="font-size:18px;">class Demon { private: int &r; }; Demon d; ///错误,无默认构造函数</span>
4)一个类拥有一个无默认构造函数的父类,或者一个既无默认构造函数也未进行类内初始化的成员
<span style="font-size:18px;">class Mem { public: Mem(const string addr):address(addr){} private: string address; }; class Demon { private: Mem m; }; class DerivedDemon:public Demon { }; Demon d; ///错误,无默认构造函数 DerivedDemon dd; ///错误,无默认构造函数</span>
5)一个类拥有一个既无显式的默认构造函数,也未进行类内初始化的常量成员成员(如int常量)
<span style="font-size:18px;">class Demonx { private: const int i; }; class Demony { private: const string s; }; Demonx dx; ///错误,无默认构造函数 Demony dy; ///ok</span>
copy construstor:
1)一个类拥有标记为delete或者无访问权限的析构函数的成员或父类
2)一个类拥有标记为delete或者无访问权限的复制构造函数的成员或父类
3)一个类显示定义了move construstor或者move operator
<span style="font-size:18px;">class Demonx { public: Demonx()=default; Demonx(Demonx &&dx)=default; }; Demonx dxa; Demonx dxb = dxa; ///错误,copy construstor被标记为delete Demonx dxc; dxc = dxa; ///错误,copy assignment operator被标记为delete</span>
copy assignment:
1)一个类拥有标记为delete或者无访问权限的复制赋值运算符的成员或父类
2)一个类拥有一个常量成员或者一个引用成员
3)一个类显式定义了move construstor或者move operator
destrustor:
一个类拥有标记为delete或者无访问权限的析构函数的父类或者成员,当一个类拥有标记为delete或private的析构函数时,它的子类其实是无法实现析构函数的,只能将其标记为delete,因为子类的析构函数会自动调用父类析构函数的。同样,当一个类拥有标记为delete或者不可访问的析构函数的成员时,它本身的析构函数也无法实现
<span style="font-size:18px;">class Demon { ~Demon=delete; }; class DerivedDemon:public Demon { public: ~DerivedDemon() { ///此析构函数会自动调用父类Demon的析构函数, ///因此错误 }; }; DerivedDemon dd; ///错误</span>
move construstor,move operator:
只有当一个类没有显式定义任何的复制构造函数,析构函数,复制赋值运算符,而且其所有成员和父类都可以移动,其析构函数可被访问,编译器词汇自动合成move construstor和move operator。(可移动是指一个成员要么是内置类型,要么提供了可访问的move construstor和move operator。常量成员和引用成员不可移动)
<span style="font-size:18px;">class Demonx { public: ~Demonx()=default; }; class Demony { public: Demony()=default; Demony(const Demony&)=delete; Demony& operator=(const Demony&)=delete; }; Demonx dax; Demonx dbx(move(dax)); ///ok,会调用copy construstor ///当move construstor或者move assignment operator被标记为delete时, ///需要使用它们时,会调用对应的copy construstor或者 copy assignment Demony day; Demony dby(move(day)); ///错误,copy construstor被标记为delete,但已经属于显式定义,因此 ///move construstor也被编译器自动标记为delete,导致调用出错</span>
当我们不需要编译器自动合成的某些函数时,可以将其标记为delete,但我们自己将其标记为delete时,除了不能调用该函数以外,属于显式定义的范畴,上面的例子已经有所说明。
class Demon { Demon(const Demon&)=delete; }; Demon d; ///错误,显式定义了一个构造函数,因此默认构造函数被标记为delete
当编译器将某个函数自动标记为delete,而我们有需要和编译器自动合成的函数一模一样的函数,可以将其标记为default以强制编译器合成,标记为default的函数虽然是编译器合成的,但也属于显式定义的范畴。而且,如果编译器无法合成该函数,例如为含有不可复制/移动的成员的类合成复制/移动构造函数,该函数会依然被标记为delete
class Demon { public: Demon(Demon &&)=default; Demon& operator=(const Demon &)=default; virtual ~Demon()=default; }; Demon d; ///错误,默认构造函数被标记为delete
class Demon { public: Demon()=default; Demon(Demon &&)=default; Demon& operator=(const Demon &)=default; virtual ~Demon()=default; }; Demon dx; Demon dy = dx; ///错误,复制构造函数被标记为delete
class Demon { public: Demon(const Demon&)=delete; virtual ~Demon()=default; }; class DerivedDemon:public Demon { public: DerivedDemon()=default; }; DerivedDemon da; ///错误,DerivedDemon依然被标记为delete综上,一般情况下,一个类如果拥有不可访问或者标记为delete的析构函数的成员或父类,那么它本身也无法实现析构函数,因为析构函数会自动调用父类和成员的析构函数,当一个类拥有不可访问或者被标记为delete的其他复制控制函数的成员或父类时时,编译器无法合成这些函数,即使我们强制合成,因为编译器合成这些函数的办法只有一个,及调用他们父类或以及成员的对应函数,但是我们还可以自己编写这些函数,但是也会有相当的不便。
总后给一个例子,说明上面这些函数的调用规则
class Demon { public: Demon():id(++num) { cout<<"Demon "<<id<<" born"<<endl; } Demon(const Demon &d):id(++num) { cout<<"Demon "<<id<<" born by copy"<<endl; } Demon(Demon &&d):id(++num) { cout<<"Demon "<<id<<" born by move"<<endl; } Demon& operator=(const Demon &d)& { cout<<"Demon "<<id<<" value changed by copy"<<endl; } Demon& operator=(Demon &&)& { cout<<"Demon "<<id<<" value changed by move"<<endl; } virtual ~Demon() { cout<<"Demon "<<id<<" died"<<endl; --num; } private: size_t id; static size_t num; }; size_t Demon::num=0; class DerivedDemon:public Demon { public: DerivedDemon():d_id(++d_num) { cout<<"DerivedDemon "<<d_id<<" born"<<endl; } DerivedDemon(const DerivedDemon& dd):Demon(dd),d_id(++d_num) { cout<<"DerivedDemon "<<d_id<<" born by copy"<<endl; } DerivedDemon(DerivedDemon &&dd):Demon(move(dd)),d_id(++d_num) { cout<<"DerivedDemon "<<d_id<<" born by move"<<endl; } DerivedDemon& operator=(const DerivedDemon &dd)& { Demon::operator=(dd); cout<<"DerivedDemon "<<d_id<<" value changed by copy"<<endl; } DerivedDemon& operator=(DerivedDemon &&dd)& { Demon::operator=(move(dd)); cout<<"DerivedDemon "<<d_id<<" value changed by move"<<endl; } ~DerivedDemon() { cout<<"DerivedDemon "<<d_id<<" died"<<endl; --d_num; } private: size_t d_id; static size_t d_num; }; size_t DerivedDemon::d_num=0; void test() { Demon *dpx = new DerivedDemon(); Demon *dpy = new DerivedDemon(); *dpx = *dpy; *dpy = move(*dpx); delete dpx; delete dpy; DerivedDemon da; DerivedDemon db=da; DerivedDemon dc(move(da)); DerivedDemon dd=move(db); da = db; dc = move(da); }运行结果:
Demon 1 born
DerivedDemon 1 born
Demon 2 born
DerivedDemon 2 born
Demon 1 value changed by copy
Demon 2 value changed by move
DerivedDemon 1 died
Demon 1 died
DerivedDemon 2 died
Demon 2 died
Demon 1 born
DerivedDemon 1 born
Demon 2 born by copy
DerivedDemon 2 born by copy
Demon 3 born by move
DerivedDemon 3 born by move
Demon 4 born by move
DerivedDemon 4 born by move
Demon 1 value changed by copy
DerivedDemon 1 value changed by copy
Demon 3 value changed by move
DerivedDemon 3 value changed by move
DerivedDemon 4 died
Demon 4 died
DerivedDemon 3 died
Demon 3 died
DerivedDemon 2 died
Demon 2 died
DerivedDemon 1 died
Demon 1 died
C++对象复制控制