智能指针的使用与陷阱

在包含指针的类中需要注意复制控制,复制指针时只复制指针中的地址,不会复制指针指向的对象。

大多数c++类采用三种方法管理指针成员:

1)指针成员采用常规指针型行为。

2)采用智能指针

3)采取值型行为

常规指针缺陷:可能会出现悬垂指针。当一个指针复制到另一个指针,两个指针指向同一个对象,当一个指针删除对象时,另一个指针不知道,所以出现悬垂指针。即使使用默认合成复制构造函数也会出现,类本身无法避免。

智能指针:加入了引用计数。引用计数跟踪该类有多少对象共享同一指针。当引用计数为0 时,删除对象。创建新类时,初始化指针并将引用计数置为1.进行复制时,增加相应引用计数值。赋值时,减少左操作数所指对象的引用计数的值(减至0,就删除对象),增加右操作数所指对象的引用计数的值。最后,调用析构函数时,减少引用计数的值。如果减至0,就删除对象。

值型类:复制时会new一个新的副本,指针所指向的对象是唯一的,每个类对象独立管理。

为了管理具有指针成员的类,必须定义三个复制控制成员:复制构造函数,赋值操作符和析构函数。这些成员可以定义指针成员的指针型行为或者值型行为。

c++出现内存问题的地方一般:

1)缓冲区溢出

2)悬垂指针/野指针

3)重复释放

4)内存泄漏

5)不配对的 new[]/delete

都可以通过智能指针很好的解决这些问题,比如:

1)->用vector/string或者自己写的buffer类管理,自动增加缓冲区大小,用成员函数操作,不直接通过野指针操作

2),3),4)->可以通过智能指针解决,只在对象析构的时候释放一次内存,引用计数为0的时候才删除指针,自动释放

5)->使用vector,自己释放内存

智能指针的陷阱:

这样的一个引用计数型智能指针目的是为了防止资源泄漏,但是只需要一个很小巧的代码就可以让这样的初衷化为乌有……

class A
{
public:
    A() {cout<<"A CON"<<endl;}
    ~A() {cout<<"A DES"<<endl;}
   void hold(CountedPtr<A> ptr)
    {
       m_ptr = ptr;
    }
private:
    CountedPtr<A> m_ptr;
};

void self_cir_area()
{
    CountedPtr<A> pA(new A());
    pA->hold(pA);
}

可以看见,一个对象A中有一个引用计数型智能指针,这样的设计可能会很常见(指向自身类型的结构体——链表)

但是,当自身循环引用发生的时候会怎么样呢? 下面就来看看这么两句代码

CountedPtr<A> pA(new A());

这里我们新建一个资源,并且把这个资源的管理权移交给pA这个引用计数型智能指针对象管理。如此,pA中的引用计数被初始化为1。

pA->hold(pA);

这里,我们把pA对象传入给实例化的A对象中的引用计数型智能指针m_ptr,m_ptr执行这样的一个成员函数:

//assignment (unshare old and share new value)
CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
           if (this != &p) {
                dispose();
                ptr = p.ptr;
                count = p.count;
                ++*count;
           }
           return *this;
}

因为这里很明显不是自身赋值,A中的m_ptr和pA不是同一个对象,所以进入if结构中调用下面的内容。dispose是用作清理,因为m_ptr并没有指向任何东西,所以第一个函数并没有真正的意义。

然后

m_ptr.ptr = pA.ptr;
m_ptr.count = pA.count;
++(*m_ptr.count);  //(*pA.count)也被++

到此,pA的引用计数为2

嗯,下面就pA这个对象理所当然的离开了作用域,调用其析构函数:

~CountedPtr () throw() {
     dispose();
}

噢,是一个转向,调用其private成员函数dispose():

很简单,将引用计数-1,由2变成1,不为0,所以if结构内的语句不被执行。

由此,我们又制造了一个完美的太空垃圾……

void dispose() {
      if (--*count == 0) {
           delete count;
           delete ptr;
      }
}

这样的循环引用问题应该是在设计的过程中就应该避免的,如果用UML语言描述

A中持有一个 引用计数型智能指针 的语义就是 这个 持有关系 是需要在 A消失的时候所持有的对象也随之消失(这正是智能指针的作用,在脱离作用域自动清除其持有的资源)。如此就构成了 组合 关系。如果要表示 聚合 关系,即有 部分-整体 关系但是部分不随整体的消失而消失,这就不是 智能指针 所表达的语义。

还有可能遇见的循环引用就是 A1 持有 A2, A2 持有 A1 的情况……

这样A1,A2中对双方的引用计数都是2,当一方“销毁”的时候,双方的应用计数都变为1,实际上并没有销毁任何东西,制造了两个完美无暇的太空垃圾~

这里又引发出一个问题,这样的资源泄漏问题实际上还是由程序员自身引起的。

C++之所以是一个很容易出错的语言,很大一部分在于其资源的管理权力全权交给了程序员。这样的权力到底是造福了程序员还是迷惑了程序员呢?

时间: 2024-10-24 01:46:53

智能指针的使用与陷阱的相关文章

OSG 智能指针陷阱

先看下这个代码,有什么问题: #include <osg/Group> #include <osg/Node> #include <osg/Geode> osg::Geode *geode= NULL; osg::ref_ptr<osg::Group> root = NULL; void createNode(){ geode = new osg::Geode; geode->setName("Hello"); root = new

C++--智能指针、逻辑操作符的陷阱

一.智能指针 内存泄漏(C++主要的Bug来源)1.动态申请堆空间,用完后不归还2.C++语言中没有垃圾回收机制3.指针无法控制所指堆空间的生命周期代码示例 #include <iostream> #include <string> using namespace std; class Test { int i; public: Test(int i) { this->i = i; } int value() { return i; } ~Test() { } }; int

智能指针陷阱

1.不使用相同的内置指针初始化多个智能指针(reset也不行) 2.不delete get() 返回的指针 3.不使用gat() 初始化 或reset 其他智能指针 4.如果使用get()返回的指针,要记住当最后一个智能指针被销毁后,指针就变得无效了. 5.if使用的智能指针管理的资源不是new分配的,要传给他们一个删除器

Boost智能指针-基础篇

简介 内存管理一直是 C++ 一个比较繁琐的问题,而智能指针却可以很好的解决这个问题,在初始化时就已经预定了删除,排解了后顾之忧.1998年修订的第一版C++标准只提供了一种智能指针:std::auto_ptr(现以废弃),它基本上就像是个普通的指针:通过地址来访问一个动态分配的对象.std::auto_ptr之所以被看作是智能指针,是因为它会在析构的时候调用delete操作符来自动释放所包含的对象.当然这要求在初始化的时候,传给它一个由new操作符返回的对象的地址.既然std::auto_pt

C++11中智能指针的原理、使用、实现

目录 理解智能指针的原理 智能指针的使用 智能指针的设计和实现 1.智能指针的作用 C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理.程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存.使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存. 理解智能指针需要从下面三个层次: 从较浅的层面看,智能指针是利用了一种叫做RAII(资

boost智能指针之shared_ptr和weak_ptr

std::auto_ptr很多的时候并不能满足我们的要求,比如auto_ptr不能用作STL容器的元素.boost的smart_ptr中提供了4种智能指针和2种智能指针数组来作为std::auto_ptr的补充. shared_ptr<boost/shared_ptr.hpp>:使用shared_ptr进行对象的生存期自动管理,使得分享资源所有权变得有效且安全. weak_ptr<boost/weak_ptr.hpp>:weak_ptr 是 shared_ptr 的观察员.它不会干

(转)C++11里的智能指针

1. std::auto_ptr有些违背c++编程思想. 已经被"不建议使用了".2. 下文转自:http://blog.csdn.net/lanergaming/article/details/24273419 c#和java中有自动垃圾回收机制,.net运行时和java虚拟机可以管理分配的堆内存,在对象失去引用时自动回收,因此在c#和jva中, 内存管理不是大问题.c++语言没有垃圾回收机制,必须自己去释放分配的堆内存,否则就会内存泄露. 我相信大部分c++开发人员都遇到过内存泄露

c++ primer笔记------智能指针

智能指针的陷阱 基本规范: 1.不使用相同的内置指针值初始化(或reset)多个智能指针.  double free 2.不delete get() 返回的指针.  double free 3.不使用get() 初始化或reset另一个智能指针.  double free 4.如果你使用get() 返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了.   空悬指针 5.如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器.    非new类型指针需要自己指定

Boost智能指针-基础知识

简单介绍 内存管理一直是 C++ 一个比較繁琐的问题,而智能指针却能够非常好的解决问题,在初始化时就已经预定了删除.排解了后顾之忧.1998年修订的第一版C++标准仅仅提供了一种智能指针:std::auto_ptr(现以废弃),它基本上就像是个普通的指针:通过地址来訪问一个动态分配的对象. std::auto_ptr之所以被看作是智能指针.是由于它会在析构的时候调用delete操作符来自己主动释放所包括的对象. 当然这要求在初始化的时候,传给它一个由new操作符返回的对象的地址.既然std::a