C++拷贝构造函数
问题引入
#include<iostream>
using namespace std;
class A{
public:
A(int a,int b):a(a),b(b){}
void printA(){
cout<<"class A.value a is:"<<a<<endl;
}
void printB(){
cout<<"class A.value b is:"<<b<<endl;
}
private:
int a;
int b;
};
int main(int argc, char *argv[]){
A a(10,20);
A b = a; // 声明一个A的变量b,并通过赋值操作给b进行赋值
a.printA();
a.printB();
b.printA();
b.printB();
return 0;
}
class A.value a is:10
class A.value b is:20
class A.value a is:10
class A.value b is:20
上面的代码中,可以看到在main函数里面存在,A b = a
的拷贝构造操作需要区分拷贝构造和赋值的区别,后面有提到,往后看,但是因为没有重载赋值运算符和拷贝构造函数,C++会自作主张的对类A生成一个默认拷贝操作
其等价于
A b = a;
// 或者写成
A b(a);
// 等价于
b.a = a.a;
b.b = a.b;
这是C++自动生成的赋值方式和拷贝构造方式,但是,如果类A里面加一个指针变量,并在析构函数里面删除这个指针时,再看操作,代码如下
#include<iostream>
using namespace std;
class A{
public:
A(int a,int b,int c):a(a),b(b){
this->c = new int(c);
}
~A(){
cout<<"~A()"<<endl;
delete c;
c= nullptr;
}
void printA(){
cout<<"class A.value a is:"<<a<<endl;
}
void printB(){
cout<<"class A.value b is:"<<b<<endl;
}
private:
int a;
int b;
int *c;
};
int main(int argc, char *argv[]){
A a(10,20,20);
A b = a; // 声明一个A的变量b,并通过赋值操作给b进行赋值
a.printA();
a.printB();
b.printA();
b.printB();
cout<<"i will end"<<endl;
return 0;
}
再次运行程序,得到结果
class A.value a is:10
class A.value b is:20
class A.value a is:10
class A.value b is:20
i will end
~A()
~A()
进程已结束,退出代码-1073740940 (0xC0000374)
再次运行,发现程序已经不能正常退出,而是出现了错误,如果正常运行的情况下,C++在退出时会调用A的析构函数,理论上是输出两次~A,但是为什么会出现了错误?从C++自定以的拷贝与赋值操作分析,A b = a
等价于
b.a = a.a;
b.b = a.b;
b.c = a.c;
在这里可以看到,b和c的指针变量都指向了同一个位置,这也就导致了在执行析构的时候指针被delete了两次,这也就直接导致了错误的原因,并且不只这一个问题,还有
- 因为b和c的指针都指向了同一个内存位置,修改b的指针属性,c也会受影响,如果我们希望这样的话就当我没说
所以,在这种情况下,在某些类的定义中,因为C++自定义的这类拷贝和赋值操作不满足我们的要求,比如有指针变量的情况,需要我们自定义拷贝构造函数
拷贝构造函数定义的格式为:
classname(const classname &var){
.....
}
如果对上面的代码加一个拷贝构造函数或者重载以下运算符的化
A(const A &src):a(src.a),b(src.b){
int tmp = *src.c;
c = &tmp;
}
运行结果为
class A.value a is:10
class A.value b is:20
class A.value a is:10
class A.value b is:20
i will end
~A()
~A()
进程已结束,退出代码0
重写拷贝构造函数是为某些通过C++默认拷贝构造函数的逻辑构造的类而去定义的,对于类的拷贝,建议写成如下形式
A b(a);
而不要写成,因为容易把他理解成==A b; b=a;==的赋值操作,但是两者是有区别的
A b = a; // 这是调用类A的拷贝构造
b = a; // 这是赋值操作
- 拷贝操作时调时自定义的拷贝构造函数或默认的拷贝构造函数
- 而赋值操作时调用赋值运算符,如果重载了的化就是重载后的,没有的话就是默认的
可以用一段代码来验证以下两者的区别
#include<iostream>
using namespace std;
class A{
public:
A(int a,int b):a(a),b(b){}
A(const A &src):a(src.a),b(src.b){
cout<<"Use Copy Constructor"<<endl;
}
A& operator=(const A &src){
cout<<"Use Operator Assign"<<endl;
if(this != &src){
a = src.a;
b = src.b;
}
return *this;
}
private:
int a;
int b;
};
int main(int argc, char *argv[]){
A a(10,20);
A b(9, 9);
A c = a; // 这是拷贝构造
b = a; // 这是赋值操作
return 0;
}
Use Copy Constructor
Use Operator Assign
进程已结束,退出代码0
注意区分!并且重载赋值运算符也是同样的原理,其重载格式为
A& operator=(const A&src){
....
}
// 右值引用形式
A& operator=(A &&src){
....
}