C++浅拷贝与深拷贝及引用计数分析
C++浅拷贝与深拷贝及引用计数分析
在C++开发中,经常遇到的一个问题就是与指针相关的内存管理问题,稍有不慎,就会造成内存泄露、内存破坏等严重的问题。不像Java一样,没有指针这个概念,所以也就不必担心与指针相关的一系列问题,但C++不同,从C语言沿袭下来的指针是其一大特点,我们常常要使用new/delete来动态管理内存,那么问题来了,特别是伴随着C++的继承机制,如野指针、无效指针使用、内存泄露、double free、堆碎片等等,这些问题就像地雷一样,一不小心就会踩那么几颗。
先来谈一下C++类中常见的浅拷贝问题,以及由此引发的double free。什么是浅拷贝?当类中的成员变量包括指针时,而又没有定义自己的拷贝构造函数,那么在拷贝一个对象的情况下,就会调用其默认拷贝构造函数,其实这个函数没做什么事,只是对其成员变量作了个简单的拷贝,也就是所谓的位拷贝,它们指向的还是同一个存储空间,当对象析构时,就会析构多次,也就是double free,下面举例说明。
class Common { public: Common() { std::cout << "Common::Common" << std::endl; } Common(const Common &r) { std::cout << "Common::Common copy-constructor" << std::endl; } ~Common() { std::cout << "Common::~Common" << std::endl; } };
类Common是个一般的类,定义了构造、拷贝构造和析构函数,在函数里输出一些log,用以跟踪函数调用情况。
class BitCopy { public: BitCopy() : m_p(new Common) { std::cout << "BitCopy::BitCopy" << std::endl; } ~BitCopy() { std::cout << "BitCopy::~BitCopy" << std::endl; if (m_p) { delete m_p; m_p = NULL; } } private: Common *m_p; };
类BitCopy就是一个浅拷贝类,成员变量是我们刚定义的类指针,构造函数实例化成员变量,析构函数delete成员变量,没有定义拷贝构造函数。
int main() { BitCopy a; BitCopy b(a); return 0; } log如下: Common::Common BitCopy::BitCopy BitCopy::~BitCopy Common::~Common BitCopy::~BitCopy Common::~Common *** Error in `./a.out': double free or corruption (fasttop): 0x0000000001f4e010 *** 已放弃 (核心已转储)
从上面的log可以看出,对象a调用了构造函数,对象b调用的是默认拷贝构造函数,最后析构了两次,从而造成double free,核心已转储即core dump。
针对以上问题,该怎么解决呢?有两个办法,一个是深拷贝,一个是引用计数。先来看一下深拷贝,深拷贝要定义自己的拷贝构造函数,在函数中给成员变量重新分配存储空间,也就是所谓的值拷贝,这样它们所指向的就是不同的存储空间,析构时不会有问题,但这种方法只适用于较小的数据结构,如果数据结构过大,多次分配存储空间之后,剩余的存储空间将逐渐减小,
下面看个例子。
class ValueCopy { public: ValueCopy() : m_p(new Common) { std::cout << "ValueCopy::ValueCopy" << std::endl; } ValueCopy(const ValueCopy &r) : m_p(new Common(*r.m_p)) { std::cout << "ValueCopy::ValueCopy copy-constructor" << std::endl; } ~ValueCopy() { std::cout << "ValueCopy::~ValueCopy" << std::endl; if (m_p) { delete m_p; m_p = NULL; } } private: Common *m_p; };
类ValueCopy是个深拷贝类,与上面例子的浅拷贝类不同的是定义了拷贝构造函数,在函数中给成员变量重新分配存储空间,下面是用法及log。
int main() { ValueCopy c; ValueCopy d(c); return 0; } Common::Common ValueCopy::ValueCopy Common::Common copy-constructor ValueCopy::ValueCopy copy-constructor ValueCopy::~ValueCopy Common::~Common ValueCopy::~ValueCopy Common::~Common
从上面的log可以看出,对象c调用了构造函数,对象d调用的是自定义拷贝构造函数,最后析构了两次而没有问题,可见深拷贝的用处所在。
引用计数与深拷贝不同,方法是共享同一块存储空间,这个对大的数据结构比较有利。使用引用计数,需要在类中定义一个成员变量专门用于计数,初始值为1,后面引用了这个对象就加1,对象销毁时引用减1,但并不真正的delete这个对象,只有当这个成员变量的值为0时才进行delete,例子如下。
class A { public: A() : m_refCount(1) { std::cout << "A::A" << std::endl; } A(const A &r) : m_refCount(1) { std::cout << "A::A copy-constructor" << std::endl; } ~A() { std::cout << "A::~A" << std::endl; } void attach() { std::cout << "A::attach" << std::endl; ++m_refCount; } void detach() { if (m_refCount != 0) { std::cout << "A::detach " << m_refCount << std::endl; if (--m_refCount == 0) { delete this; } } } private: int m_refCount; }; class B { public: B() : m_pA(new A) { std::cout << "B::B" << std::endl; } B(const B &r) : m_pA(r.m_pA) { std::cout << "B::B copy-constructor" << std::endl; m_pA->attach(); } ~B() { std::cout << "B::~B" << std::endl; m_pA->detach(); } private: A* m_pA; };
类A用到了引用计数,构造和拷贝构造函数都初始化为1,attach()函数为引用加1,detach()函数为引用减1,当引用计数值为0时delete对象。类B中的成员变量有个指针指向A,拷贝构造函数中调用了attach(),析构函数中调用了detach(),这样也是一种保护,不会有内存泄露,也不会有double free,log如下。
int main() { B e; B f(e); return 0; } A::A B::B B::B copy-constructor A::attach B::~B A::detach 2 B::~B A::detach 1 A::~A
从log中可以看出,指针成员变量的引用计数为2,这是正确的,最后正确delete,没有问题。
在类中只要有指针成员变量,就要注意以上问题,另外,operator=这个赋值操作符也要在适当的时候进行重载。有时候,如果想规避以上问题,可以声明拷贝构造函数和operator=操作符为private而不去实现它们。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
相关文章
- vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
- 这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
- 本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
- 整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
- 这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
- 这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
- 这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
- 这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
- 这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
- 虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
- 这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- map容器是C++ STL中的重要一员,删除map容器中value为指定元素的问题是我们经常与遇到的一个问题,下面这篇文章主要给大家介绍了关于利用C++如何删除map容器中指定值的元素的相关资料,需要的朋友可以参考借鉴,下面来一起看看吧。...2020-04-25
- 这篇文章主要介绍了C++ 约瑟夫环问题案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下...2021-08-15
- 这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本篇文章是对C++中的常见编译错误进行了详细的分析介绍,需要的朋友参考下...2020-04-25
- 在本篇内容里小编给大家分享了关于C++实现递归函数的教学步骤,需要的朋友跟着参考下。...2020-04-25