copy构造函数定义
copy构造函数的定义:如果一个构造函数的第一个参数是自身类类型的引用,且额外参数都是默认值,则此构造函数时copy构造函数(摘自c++primer)。
class Foo { public: Foo(); Foo(const Foo&); //copy构造函数 }
copy构造函数的形参必须引用类型:如果不是引用类型,为了调用copy构造函数,必须copy他的实参,但copy实参又需要copy构造函数,一直循环。
copy构造函数何时发生:
1.显示地以一个对象的内容作为另一个类对象的初值(以"="的形式或者"()")
2.对象作为一个实参传递给一个非引用类型的形参
3.对象作为一个函数的返回值,返回类型为非引用类型
4.用花括号列表初始化一个数组中的元素或一个聚合类的成员
下面的例子验证以上四点(第三点似乎有些编译器优化了结果)
1 class AA 2 { 3 public: 4 AA(){cout<<"default"<<endl;} 5 // private: 6 AA(const AA&){cout<<"copy"<<endl;} 7 public: 8 int a; 9 }; 10 11 void f_re(AA&a) 12 { 13 14 } 15 16 void f_nore(AA a) 17 { 18 19 } 20 21 AA f_ret() 22 { 23 AA f; 24 return f; 25 } 26 27 int main() 28 { 29 30 AA a,b,c; 31 cout<<1<<endl; 32 AA m=a; 33 cout<<"2_re"<<endl; 34 f_re(a); 35 cout<<"2_nore"<<endl; 36 f_nore(a); 37 cout<<3<<endl; 38 f_ret(); 39 cout<<4<<endl; 40 vector<AA> d{a,b,c}; 41 return 0; 42 }
这是codeblock上编译的(),从编译结果看,
f_ret();
并没有产生copy构造函数,然后该在vs2013单独运行了
f_ret();
在这里copy构造函数是确实存在,我个人觉得应该是codeblock的编译优化了结果,所以我将试着把copy构造函数设为private,再次运行
1 class AA 2 { 3 public: 4 AA(){cout<<"default"<<endl;} 5 private: 6 AA(const AA&){cout<<"copy"<<endl;} //设为private 7 public: 8 int a; 9 };
运行直接出错
所以,对象作为一个函数的返回值,返回类型为非引用类型需要copy构造函数是确定的。
还有一点就是花括号列表初始化数组,一个元素是调用copy构造函数两次的(这里我个人觉得可能用到了中间变量,具体原因我也没找出来)
区分直接初始化与copy初始化
一般来说使用等号(=)初始化一个变量,采用的是copy初始化,copy初始化一定用到copy构造(或者移动构造)
string s="hello"; //这个等价于两个操作 string temp("hello");string s=temp;
对于类类型的对象,拷贝初始化就相当于先创建一个对象(执行构造函数)再进行拷贝(执行拷贝构造函数)
直接初始化一般要求编译器使用普通的函数匹配来选择与之最匹配的构造函数(可以是构造函数也可以是copy构造函数)
string s1(s); //copy构造
string s2("hello"); //构造函数
合成copy构造函数
一般来说如果一个类没有显式提供copy构造函数,编译器根据需要会提供一个合成的默认copy构造函数(这里一定要清楚是编译器觉得有必要才会生成)。
这个必要性由编译器有没有展现"bitwise copy semantic(位逐次copy)"来决定的。
只有在下面四种情况是不展现位逐次copy的,这个时候会逐个执行成员初始化,完成必要工作(合成copy构造):
1.当class内含一个成员对象,而这个成员对象声明了一个copy构造函数;
2.当class继承自一个基类,该基类存在一个copy构造函数;
3.当class声明了一个或多个虚函数;
4.当class派生自一个继承串链,其中有一个或多个数是虚基类。
所以当一个类没有显式的copy构造时,但采用位逐次copy时,会出现一个浅copy问题。
深拷贝与浅拷贝
深copy与浅copy主要在于copy的过程中,对资源处理的形式不同。
浅copy就是位逐次copy,直接将对象里成员的值赋值另一个对象,大多数情况下这是可以的,但是当成员对象存在资源(指针),这时他将指向同一个位置,这就存在一定的风险,当一个对象删除了,他的资源自然也就释放,但是另一个对象却还在,就会出现野指针了。
深copy则是重新开辟空间,存放资源,因而不存在不同对象指针指向同一个资源。
一般而言copy构造函数与赋值运算符同时存在,要自定义时同时自定义。当存在需要自定义析构函数时一般都要自定义copy构造与赋值运算符(理由同上述解释)。(三/五法则)
合成拷贝控制成员可能是删除
本质上,当一个类的成员不能默认构造、copy,赋值或析构时,对应的成员函数将被定义为删除。
一个成员删除或者不可访问析构函数会导致合成的默认构造与copy构造函数定义为删除,这是因为对象创建时会先检查是否能被销毁,如果不能就拒绝创建,因而一个析构函数删除的类型,只能通过动态分配内存。
对于具有引用成员或者const成员,合成copy构造与赋值运算符也是定义为删除,毕竟不能将一个新值赋给const对象,给一个引用对象赋值也不改变赋值前的对象本身。
class NameObject { public: // NameObject()=default; NameObject(string a,int b):nameValue(a),objectValue(b){cout<<nameValue<<" "<<objectValue<<endl;} private: string& nameValue; const int objectValue; }; int main() { // NameObject b; 出错,引用或者const导致默认构造函数删除; NameObject a("abc",12); NameObject b("bcd",10); //c=a; 出错,引用或者const导致合成赋值运算符删除; NameObject c(a); //成员时const或引用不影响copy构造函数 return 0; }