类类型的拷贝构造函数与析构函数,成员中含有动态内存指针时的处理(C++11)

以下讨论的符号约定

假设定义类类型

class A{
成员a
成员b
成员c//指向动态内存的指针
成员d//普通指针(指向静态内存)
};
及一个A的变量 e
~~~c
A e;

下面讨论的是通过e构造新的A的变量f的一些相关问题

得出以下结论的实验代码(建议先看后面的内容)

#include <iostream>
#include <string>

using namespace std;
class A
{
public:
   A();
   A(const A& l):a(l.a),b(l.b),c(l.c),d(l.d){
	c = new int(*l.c);	
   }
public:
    int a;
    string b;
    int* c;//动态内存成员
    //int* c= new int;这种形式也可以
    int* d;//普通指针成员
    ~A(){
        delete c;    
     }
};
A::A()
{
   c = new int;//c的定义为int* c或int* c= new int时都可以这么写
}
//A::A(){}//仅当c的定义为int* c= new int时可以这么写

int main( int argc, char** argv )
{
	for(int i= 1;i != 2;++i)
    {
 		int x = 2;
        int y = 8;
        A e;
        e.a =5;
        e.b ="dhsu";
        *e.c =y;
		e.d =&x;

		A f(e) ;
		cout <<"e: "<<e.a<<" "<<e.b<<" "<<*e.c<<" "<<*e.d<<endl;
        cout <<"f: "<<f.a<<" "<<f.b<<" "<<*f.c<<" "<<*f.d<<endl;

        e.a =8;
        e.b ="kkkk";
        *e.c = 3;
		*e.d =4;

    	cout <<"e: "<<e.a<<" "<<e.b<<" "<<*e.c<<" "<<*e.d<<endl;
		cout <<"f: "<<f.a<<" "<<f.b<<" "<<*f.c<<" "<<*f.d<<endl;
    }
   return 0;
}

tips:

1, new开辟的内存称为动态内存
2, delete 的作用是释放动态(new)内存,delete静态(非new)内存是未定义的行为.
3,同一块动态内存只能delete一次,指向同一动态内存的多个指针,当一个指针被delete后,其他指针将无效,调用时将发生未知错误

什么是拷贝构造

拷贝构造是指当采用A f(e);或者A f=e;的形式借助已有的e构造新的变量f时发生的构造行为.这种构造行为将通过拷贝构造函数(合成的或自己定义的)完成.

注意拷贝构造函数以及其他构造函数都是在构造时调用的,就是说在A f…;这一行命令中调用的,其他位置不会发生.

1,将一个对象做为实参传给函数的非引用形参.
2,以及一个返回类型为非引用类型的函数返回一个对象.
3,以及对标准库容器调用insert以及push.

以上三点会调用拷贝构造函数,但本质上也是在A f…;这一行命令中调用的.
<<c++ primer>>还描述了其他不常用的情况,没有细究,但本质上应该都是A f…;这一命令行中才会调用.

拷贝构造:
A f(e);
或 A f=e;

如果代码写成A f; f=e;A f; f(e);则不会调用拷贝构造函数

A f; 调用的是A()形式的构造函数(默认或者自定义的)
f=e; 调用的是运算符=,默认的或是自定义的

A f;调用的是A()形式的构造函数(默认或者自定义的)
f(e);以上调用的是A(A)形式的函数,很像拷贝构造函数,但却不是,而且都不是构造函数.(此处可能有误,没有细究)

为什么要自定义析构函数

当类类型的成员是指向动态内存的指针时,编译器自动合成(以下简称:合成的)析构函数不能释放该动态内存.因此需要自己定义析构函数,显式的释放动态内存(使用delete)

为什么要自定义拷贝构造函数

若类类型的成员中有的是指向动态内存的指针,借助已有的e构造新的变量f时,编译器自动合成(以下简称:合成的)拷贝构造函数,会将f和e中相对应的动态内存指针成员指向同一个内存,而不是为f中的指向动态内存的指针开辟新的成员.
很多时候我们希望两者的动态内存是各自独有的,因此要自定义拷贝构造函数实现这个目的.

拷贝构造函数与析构函数的联系

当类类型的成员中有的是指向动态内存的指针时,为了释放其内存,必然要自己定义析构函数,但是如果采用编译器自己合成的拷贝构造函数的话,e和f的动态内存成员指向的内存是同一个,当e或f内存释放后,另一方的相应成员指向的内存也就被释放了,因此会发生错误.
为保证不发生此类错误,e和f的动态内存成员指向的内存不能是同一个,所以要采用自定义的拷贝构造函数
这也就是<<c++ primer>>中说"如果定义了析构函数,那么几乎一定要定义拷贝构造函数"的原因,几乎之外的例外是,你的代码中不会用到拷贝构造.
总结一下:
逻辑就是,因为有动态内存成员,所以要自定义析构函数,所以要自定义拷贝构造函数.

成员是普通指针的情况

以上一系列工作都是因为类类型有成员是指向动态内存的指针,那我们很自然的会好奇,如果含有int* pt 这样的普通指针成员时,会怎样?
结果是整洁的.
对于普通指针,无论在是否使用默认的还是定义的拷贝构造函数,都会共享内存,无论使用默认的还是定义的析构函数都不能释放其内存(其以普通指针的形式释放内存,因此不用担心内存泄漏,但要记得会共享内存)
这里的逻辑是:
因为普通指针会自动且恰当的解决内存管理问题,所以就不需要析构,后面的一些列麻烦自然也就不存在了
那当我们不希望e和f的普通指针成员共享内存时,要怎么做呢,答案是:不要用拷贝构造!

自定义的析构函数该怎么写

只谈怎么释放动态内存指针成员的内存,方法是显然的.直接在析构函数函数体内delete掉就可以了.

~A()
{
	delete c;   
}

自定义拷贝构造函数该怎么写

形参要为该类类型的const 引用,例如A(const s& l ).
定义的拷贝构造函数中,对于动态分配内存的成员,应当在函数体中为其显式的开辟内存,然后赋值,即
A(const A& l):a(l.a),b(l.b){
c = new int(*l.c);
}
而如果采用
A(const A& l):a(l.a),b(l.b),(c(l.c)) {}
或者
A(const A& l):a(l.a),b(l.b){
c =l.c;
}
则与默认拷贝构造函数没有差别,动态分配内存的成员将共享同一块内存.

正确形式:成员c不会共用内存
A(const A& l):a(l.a),b(l.b),d(l.d)
{
	c = new int(*l.c);
}
错误形式1:成员c会共用内存
A(const A& l):a(l.a),b(l.b),(c(l.c)),d(l.d)
{
}
错误形式2:成员c会共用内存
A(const A& l):a(l.a),b(l.b),d(l.d)
{
	c =l.c ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值