`

浅谈C++的智能指针

    博客分类:
  • C++
阅读更多

原文出处:http://www.cppblog.com/yearner/archive/2008/11/09/66447.html

浅谈C++的智能指针

内存泄露是C++程序员都头疼的大问题。C++缺乏像JAVAC#一样,拥有GC这么一项有利的武器,它将内存管理的部分权限交给了程序员。虽然GC的存在节约了开发、排错的时间与成本,但是C++为了追求运行速度而20年来坚决不予补充进其标准。(题外话:C++通过加大开发难度去换取执行速度的做法,在现在看来不知是否能给与正面的评价,还是留给将来再说吧。)

 
从此,在堆上申请了内存忘了释放、所造成的内存泄露的问题就一直困扰着C++程序员。也许为了稍许弥补没有垃圾回收器所造成的开发门槛高,各大厂商开发的C++库中都像COM学习引入智能指针试图解决部分目前存在的问题。

 
智能指针是存储指向动态分配(堆)对象指针的类, 用于生存期控制, 能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

 
说到智能指针,我们一定要看看标准C++库提供的搞笑的智能指针:auto_ptr

 
标准库中提供了C++程序的基本设施。虽然C++标准库随着C++标准折腾了许多年,直到标准的出台才正式定型,网上评论C++标准库时都说:在标准库的实现上却很令人欣慰得看到多种实现,并且已被实践证明为有工业级别强度的佳作。但目前的标准C++中,只有一种独苗智能指针:std::auto_ptr

  auto_ptr
指针是一个RAII对象,它初始化时获得资源,析构时自动释放资源(生命期结束).它的缺点数不胜数:
1
auto_ptr要求一个对象只能有一个拥有者,严禁一物二主
2
、缺少对引用数和数组的支持。
3
、不可将auto_ptr对象作为STL容器的元素。C++标准明确禁止这样做,否则可能会碰到不可预见的结果。(这一条晕死一大片)。
4
auto_ptr在被复制的时候会传输所有权

反正由此可见:标准库的智能指针就是无甚大用。

   
在这样的情况下,C++标准委员会自然需要考虑引入新的智能指针。目前由C++标准委员会库工作组发起的Boost 组织开发了Boost系列智能指针。

 
Boost中的智能指针有五种: scoped_ptrscoped_arrayshared_ptrshared_arrayweak_ptr.

4种完全是针对标准库中的auto_ptr提出解决方案,如:scope_ptr是针对“auto_ptr在被复制的时候会传输所有权这一弱点提出的。最后一种没见过,看名字像是弱引用智能指针,我怀疑是不是类似于JAVA中弱引用一样,有待进一步学习。

 

 

Smart PointerC++中的一个大题目,要说清楚他的所有好处很需要费点力气。我就一个功能一个功能的说。有我理解不透的地方希望大家指点。

1.copy-to-write
当生成一个C++ object的时候如果这个class很大,这个object会占用很多空间。那么每生成一个就占用一片空间,这样会占用很多系统资源。同时降低效率。一个解决方法就是对用拷贝构造函数生成的object,让他不存储数据,而只存储一个指向原来object数据的指针 这样空间就节省了很多。但问题在于这样两个object完全联结在了一起。如果修改了其中一个,另一个也跟着变了。所以这种方法不可取。这里讲的 copy-to-write技术就是解决这类问题的方法。当通过引用一个已有object去拷贝构造新object时,新object只有一个指向已有 object指针。这两个object共享数据。直到其中一个需要修改数据的时候,再把这两块数据分离。这里举一个最简化的例子。假设一个class CLargeObject,里面存有很多数据。我们用一个inner class来把所有数据放在一起,叫CDataCData里面存有大量数据,例如一个数据库。这里用最简单的模型来表示,假设只有一个整数int m_nVal; CData里面需要包含另一个变量。叫作索引数目(reference count)。它记录了指向这个CData object的来自CLargetObject类的指针各数。也就是说,总共有多少CLargeObjectobject正在引用着当前的CData object

class CLargeObject
{
private:
    struct CData
    {
    private:
        int m_nVal;
        int m_nReferenceCount;
    }
};

对于每个CLargeObjectobject,我们用一个CData类的指针来指向其数据。
CData *m_pData;

CLargeObject
至少有两个构造函数。第一个是标准的构造函数,初始化其数据。这时数据是唯一的,所以必须新生成一个CDataobject来存储数据。
CLargeObject::CLargeObject(int nVal)
{
    m_pData = new Data(nVal);
}
而对于CData类的构造函数而言,初始化他的CLargeObject是第一个指向他的,这一时刻索引数目m_nReferenceCount1
CLargeObject::Data::Data(int nVal) : m_nVal(nVal), m_nReferenceCount(1) {}

CLargeObject
的第二个构造函数是拷贝构造(copy constructor)。这样生成的object不需要有新的数据,和已有的object共享数据就可以了。这是索引数目需要加1。表示又有一个object指向当前的CData了。
CLargeObject::CLargeObject(const CLargeObject &ob) // copy constructor
{
    ob.m_pData->m_nReferenceCount++;
    m_pData = ob.m_pData;
}


这样CLargeObject就构造好了,使用了可能的最少的内存。下面看看他的析够函数(destructor)。当一个objectdelete的时候,它的数据不一定无效,如果别的object还在引用着这个数据,数据需要留下来。当然,数据的索引数目无论如何都要减1
CLargeObject::~CLargeObject()
{
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
}

下面看一看赋值操作。先说用已有的CLargeObject赋值给这个CLargeObject。这时当前CLargeObject里面的数据要指向已有的这个object,就搞定了。
CLargeObject& CLargeObject::operator = (const CLargeObject& ob)    // copy assignment
{
    ob.m_pData->m_nReferenceCount++;
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
    m_pData = ob.m_pData;

    return *this;
}

再来看看如何对CLargeObject里面的数据进行真正的修改。这样就一定需要对当前的object独立操作了,否则就影响到了其它指向同一块数据的CLargeObject。这样CData类需要一个新的函数,生成只用于当前CLargetObject的数据。如果当前的引用数目是1,那么当然这个CData就是只用于这个CLargeObject的了。否则就重新new一个CData返回。

        Data* CLargeObject::CData::get_own_copy()    // clone if necessary
        {
            if (m_nReferenceCount==1)
                return this;
            m_nReferenceCount--;
            return new Data(m_nVal);
        }
CLargeObject
修改前用这个函数得到唯一的object,然后对它赋值。
void CLargeObject::SetVal(int nNewVal)
{
    m_pData = m_pData->get_own_copy();
    m_pData->m_nVal = nNewVal;
}
对于所有可能改变CData值的操作,都需要用这种方法。

下面是只读函数,简单。直接返回值,什么特殊的都不用作。
int CLargeObject::GetVal() const
{
    return m_pData->m_nVal;
}


这样copy-to-write技术就实现了。下面把完整的程序写一下:
class CLargeObject
{
public:
    CLargeObject(int nVal);
    CLargeObject(const CLargeObject &ob);
    ~CLargeObject();

    CLargeObject& operator = (const CLargeObject& ob);
    void SetVal(int nNewVal);
    int GetVal() const;
private:
    struct Data
    {
    public:
        Data(int nVal) : m_nVal(nVal), m_nReferenceCount(1) {}
    private:
        friend class CLargeObject;
        Data* get_own_copy()    // clone if necessary
        {
            if (m_nReferenceCount==1)
                return this;
            m_nReferenceCount--;
            return new Data(m_nVal);
        }

        // control variables.
        int m_nReferenceCount;
    
        // actual data portion
        int m_nVal;
    };

    Data *m_pData;
};

CLargeObject::CLargeObject(int nVal)
{
    m_pData = new Data(nVal);
}

CLargeObject::CLargeObject(const CLargeObject &ob) // copy constructor
{
    ob.m_pData->m_nReferenceCount++;
    m_pData = ob.m_pData;
}

CLargeObject::~CLargeObject()
{
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
}

CLargeObject& CLargeObject::operator = (const CLargeObject& ob)    // copy assignment
{
    ob.m_pData->m_nReferenceCount++;
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
    m_pData = ob.m_pData;

    return *this;
}

void CLargeObject::SetVal(int nNewVal)
{
    m_pData = m_pData->get_own_copy();
    m_pData->m_nVal = nNewVal;
}

int CLargeObject::GetVal() const
{
    return m_pData->m_nVal;
}


很多存储数据的系统class,如stringCString等都有这种设计。所以记住这个应用是很有必要的。

 

分享到:
评论

相关推荐

    浅谈C_C++内存泄漏及其检测工具

    其次,使用现代C++提供的智能指针(例如std::unique_ptr和std::shared_ptr)可以减少手动管理内存的复杂性,并降低内存泄漏的风险。然而,智能指针也有其局限性,并不能完全解决所有类型的内存泄漏问题。 C/C++的...

    浅谈C++的浅拷贝出现的错误

    在C++编程中,浅拷贝(shallow copy)是一种对象复制的方式,它涉及到的是对象的数据成员的直接复制。然而,当对象包含动态分配...通过使用智能指针、检查空指针或重写拷贝构造函数,可以有效地防止浅拷贝带来的错误。

    浅谈C/C++内存泄露及其检测工具

    在STL(Standard Template Library)中,已经提供了如`shared_ptr`、`unique_ptr`和`weak_ptr`等智能指针类型,它们在对象不再使用时自动删除指向的对象,从而减少内存泄漏的可能性。然而,尽管Smart Pointer技术...

    浅谈内存泄漏_收藏浅谈内存泄漏_收藏

    此外,遵循良好的编程实践,如使用智能指针(如C++11及更高版本的`std::unique_ptr`和`std::shared_ptr`)可以自动管理内存,减少泄漏的可能性。在C++中,还可以考虑使用RAII(Resource Acquisition Is ...

    侯克林 C++.rar

    同时,可能还涉及了智能指针(如unique_ptr、shared_ptr)的使用,它们可以自动管理内存,防止内存泄漏,是现代C++编程的重要工具。 综上所述,侯克林老师的课件不仅覆盖了C++基础中的关键部分,还强调了STL的实用...

    C++内存管理.doc

    #### 2.3 浅谈C/C++内存泄漏及其检测工具 ##### 2.3.1 内存泄漏的定义 内存泄漏是指已分配的内存没有被释放,导致随着时间的推移,可用内存逐渐减少。 ##### 2.3.2 内存泄漏的发生方式 内存泄漏通常发生在以下...

    浅谈CC++内存泄漏及其检测工具

    ### 浅谈C/C++内存泄漏及其检测工具 #### 内存泄漏的定义与常见原因 内存泄漏在C/C++编程中是一个常见的问题,尤其是在管理动态分配的内存时。所谓内存泄漏,通常指的是堆内存的泄漏。堆内存是程序运行过程中动态...

    浅谈内存泄漏

    2. **使用智能指针**:C++11引入了智能指针,如`std::unique_ptr`和`std::shared_ptr`,它们可以自动管理内存,有效避免内存泄漏。 3. **资源管理**:对于非内存资源,使用RAII(Resource Acquisition Is ...

    浅谈内存泄漏(1)——内存泄漏的定义

    解决内存泄漏的方法包括采用智能指针来自动管理内存,使用垃圾回收(Garbage Collection)机制,或者编写更严格的代码审查和测试流程,确保在所有可能的退出路径上都正确地释放了内存和资源。在C++中,RAII...

    浅谈Java语言评价胜出的8大技术优势

    ### 浅谈Java语言评价胜出的8大技术优势 #### 1. 强大的API支持 Java提供了非常丰富的API支持,包括网络编程中的Socket API、数据库操作中的SQL API、图形用户界面的Swing和AWT API等。这些API不仅功能强大而且...

    高质量C++/C编程指南.doc

    至于内存管理这一C++编程中的“烫手山芋”,作者将向读者展示智能指针的使用、内存池的构建等高级技巧,这些都有助于减少内存泄漏和悬挂指针问题。 除了基础部分,这本指南还涉猎了模板元编程、标准模板库(STL)的...

    浅谈数据结构课程的教学改革.pdf

    数据结构课程是计算机科学与技术专业的核心基础课程之一,同时也为操作系统、数据库、软件工程和人工智能等课程打下基础。数据结构的理论性强,内容抽象,涉及的概念、算法多且复杂,需要学生具备较强的逻辑思维能力...

    浅谈C语言在机电类课程中的应用.pdf

    第二,C语言拥有丰富的运算符,包括34种运算符,以及多种数据类型,如整型、实型、字符型、数组类型、指针类型、结构体类型和共用体类型,这些都为机电类的复杂编程任务提供了便利。第三,C语言的表达方式十分灵活...

    浅谈返回函数内部new分配的内存的引用

    为了解决这个问题,C++标准库提供了智能指针,如`std::unique_ptr`和`std::shared_ptr`,它们可以自动管理对象的生命周期。使用这些智能指针,可以确保在适当的时候自动释放内存,从而避免内存泄露。例如: ```cpp ...

    浅谈在函数中返回动态的内存

    在实际编程中,推荐使用智能指针(如`std::unique_ptr`或`std::shared_ptr`)来自动管理动态内存,以减少手动管理内存时可能出现的错误。此外,遵循RAII(Resource Acquisition Is Initialization)原则也是避免内存...

    冲刺BAT练习题

    ### 第一讲:浅谈国内笔试面试风格及准备方法 1. **实现一个Memcpy函数** - `memcpy`函数用于在内存区域之间复制固定数量的字节。 - 实现时需注意处理边界情况和防止缓冲区溢出等问题。 2. **STL中vector的实现...

    我的编程感悟(中文PDF)(共37M二分卷)分卷二

    6.1 浅谈代码优化 138 6.2 并不仅仅是汇编 139 6.2.1 在算法实现时减少上下文的依赖关系 139 6.2.2 低效的静态变量 140 6.2.3 数据的组织 141 6.2.4 消除除法 142 6.2.5 避免过大的循环 144 6.3 汇编和C/C++的混合...

    我的编程感悟(中文PDF)(共37M二分卷)分卷一

    6.1 浅谈代码优化 138 6.2 并不仅仅是汇编 139 6.2.1 在算法实现时减少上下文的依赖关系 139 6.2.2 低效的静态变量 140 6.2.3 数据的组织 141 6.2.4 消除除法 142 6.2.5 避免过大的循环 144 6.3 汇编和C/C++的混合...

    一个牛人给java初学者的建议

    #### 浅谈Java及其应用 Java自诞生以来,已经成为全球范围内广泛使用的编程语言之一。对于初学者而言,理解Java的基础知识及其应用场景至关重要。 ### Java语言简介 Java是一种面向对象的语言,其设计初衷是为了...

Global site tag (gtag.js) - Google Analytics