C++浅拷贝与深拷贝及引用计数分析
C++浅拷贝与深拷贝及引用计数分析
在C++开发中,经常遇到的一个问题就是与指针相关的内存管理问题,稍有不慎,就会造成内存泄露、内存破坏等严重的问题。不像Java一样,没有指针这个概念,所以也就不必担心与指针相关的一系列问题,但C++不同,从C语言沿袭下来的指针是其一大特点,我们常常要使用new/delete来动态管理内存,那么问题来了,特别是伴随着C++的继承机制,如野指针、无效指针使用、内存泄露、doublefree、堆碎片等等,这些问题就像地雷一样,一不小心就会踩那么几颗。
先来谈一下C++类中常见的浅拷贝问题,以及由此引发的doublefree。什么是浅拷贝?当类中的成员变量包括指针时,而又没有定义自己的拷贝构造函数,那么在拷贝一个对象的情况下,就会调用其默认拷贝构造函数,其实这个函数没做什么事,只是对其成员变量作了个简单的拷贝,也就是所谓的位拷贝,它们指向的还是同一个存储空间,当对象析构时,就会析构多次,也就是doublefree,下面举例说明。
classCommon
{
public:
Common()
{
std::cout<<"Common::Common"<
类Common是个一般的类,定义了构造、拷贝构造和析构函数,在函数里输出一些log,用以跟踪函数调用情况。
classBitCopy
{
public:
BitCopy()
:m_p(newCommon)
{
std::cout<<"BitCopy::BitCopy"<
类BitCopy就是一个浅拷贝类,成员变量是我们刚定义的类指针,构造函数实例化成员变量,析构函数delete成员变量,没有定义拷贝构造函数。
intmain()
{
BitCopya;
BitCopyb(a);
return0;
}
log如下:
Common::Common
BitCopy::BitCopy
BitCopy::~BitCopy
Common::~Common
BitCopy::~BitCopy
Common::~Common
***Errorin`./a.out':doublefreeorcorruption(fasttop):0x0000000001f4e010***
已放弃(核心已转储)
从上面的log可以看出,对象a调用了构造函数,对象b调用的是默认拷贝构造函数,最后析构了两次,从而造成doublefree,核心已转储即coredump。
针对以上问题,该怎么解决呢?有两个办法,一个是深拷贝,一个是引用计数。先来看一下深拷贝,深拷贝要定义自己的拷贝构造函数,在函数中给成员变量重新分配存储空间,也就是所谓的值拷贝,这样它们所指向的就是不同的存储空间,析构时不会有问题,但这种方法只适用于较小的数据结构,如果数据结构过大,多次分配存储空间之后,剩余的存储空间将逐渐减小,
下面看个例子。
classValueCopy
{
public:
ValueCopy()
:m_p(newCommon)
{
std::cout<<"ValueCopy::ValueCopy"<
类ValueCopy是个深拷贝类,与上面例子的浅拷贝类不同的是定义了拷贝构造函数,在函数中给成员变量重新分配存储空间,下面是用法及log。
intmain()
{
ValueCopyc;
ValueCopyd(c);
return0;
}
Common::Common
ValueCopy::ValueCopy
Common::Commoncopy-constructor
ValueCopy::ValueCopycopy-constructor
ValueCopy::~ValueCopy
Common::~Common
ValueCopy::~ValueCopy
Common::~Common
从上面的log可以看出,对象c调用了构造函数,对象d调用的是自定义拷贝构造函数,最后析构了两次而没有问题,可见深拷贝的用处所在。
引用计数与深拷贝不同,方法是共享同一块存储空间,这个对大的数据结构比较有利。使用引用计数,需要在类中定义一个成员变量专门用于计数,初始值为1,后面引用了这个对象就加1,对象销毁时引用减1,但并不真正的delete这个对象,只有当这个成员变量的值为0时才进行delete,例子如下。
classA
{
public:
A()
:m_refCount(1)
{
std::cout<<"A::A"<attach();
}
~B()
{
std::cout<<"B::~B"<detach();
}
private:
A*m_pA;
};
类A用到了引用计数,构造和拷贝构造函数都初始化为1,attach()函数为引用加1,detach()函数为引用减1,当引用计数值为0时delete对象。类B中的成员变量有个指针指向A,拷贝构造函数中调用了attach(),析构函数中调用了detach(),这样也是一种保护,不会有内存泄露,也不会有doublefree,log如下。
intmain()
{
Be;
Bf(e);
return0;
}
A::A
B::B
B::Bcopy-constructor
A::attach
B::~B
A::detach2
B::~B
A::detach1
A::~A
从log中可以看出,指针成员变量的引用计数为2,这是正确的,最后正确delete,没有问题。
在类中只要有指针成员变量,就要注意以上问题,另外,operator=这个赋值操作符也要在适当的时候进行重载。有时候,如果想规避以上问题,可以声明拷贝构造函数和operator=操作符为private而不去实现它们。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!