【总结】C++智能指针

Posted on Posted in 计算机1,497 views

    指针是C的灵魂,在C++里面指针也有着很重要的地位,但是指针的使用带来了很多问题,比如内存泄漏。C++没有自动内存管理机制,在堆上面分配的资源需要自己释放。所以在c语言中的malloc/free和C++中的new/delete总是需要成对出现。我们平常看到的指针通常被叫做裸指针,在编写程序时因为各种原因可能会出现不释放的问题,为此智能指针就有他的用处了。智能指针并不是指针,它仅仅是一个栈对象而已,在栈对象的生命周期结束时,智能指针调用析构函数释放其管理的堆内存。所有的智能指针都重载了'operator->'操作符,用来返回其管理对象的引用。从而可以执行所管理对象的一些操作。

零、准备

1、C++11前智能指针使用方法:

    std::auto_ptr    // C++11中被弃用

    boost::scoped_ptr

    boost::shared_ptr 

    boost::intrusive_ptr

    boost::scoped_array 

    boost::shared_array 

    boost::weak_ptr

2、C++11中的智能指针:

    shared_ptr

    weak_ptr

    unique_ptr

3、两个重要方法

    A、get()

        访问智能指针包含的裸指针引用。

    B、reset()

        若传递参数为空或NULL 则智能指针会释放当前管理的内存。

        若传递参数为一个对象 则智能指针会释放当前管理的内存,管理新传入的对象。

4、定义一个类用于测试

class Evan {
 public:
    Evan() : key_(0), value_("") {
        cout<< "\t Construct Evan with no parameter!" << endl;
    }

    Evan(int key, string value) : key_(key), value_(value) {
        cout<< "\t Construct Evan with [" << key << "," << value << "]!" << endl;
    }

    ~Evan() {
        cout<< "\t Destruct Evan object!" << endl;
    }

    void PrintEvan() {
        cout << "\t PrintEvan: key = " << key_ << ", value = " << value_ << endl;
    }

    void SetKey(int key) {
        key_ = key;
    }

    void SetValue(string value) {
        value_ = value;
    }
 
  private:
    int key_;
    string value_;
};

5、程序结构

#include <iostream>
#include <string>
using namespace std;

void TestAutoPtr();
void TestBoostScopedPtr();
void TestBoostSharedPtr();
void TestBoosTintrusivePtr();
void TestBoostScopedArray();
void TestBoostSharedArray();
void TestBoostWeakPtr();
void TestSharedPtr();
void TestWeakPtr();
void TestUniquePtr();

int main(int argc, char const *argv[])
{
 TestAutoPtr();
 // boost
 TestBoostScopedPtr();
 TestBoostSharedPtr();
 TestBoosTintrusivePtr();
 TestBoostScopedArray();
 TestBoostSharedArray();
 TestBoostWeakPtr();
 // C++ 11
 TestSharedPtr();
 TestWeakPtr();
 TestUniquePtr();
 
 return 0;
}

一、std::auto_ptr

1、在namespace std中 添加头文件 #include <memory>即可使用。一般用于管理单个堆内存对象。

2、代码

#include <memory>
void TestAutoPtr() {
    cout << "TestAutoPtr" <<endl;
    auto_ptr<Evan> evan_auto_ptr(new Evan(1, " xia"));  
    if (evan_auto_ptr.get())                    // 判断智能指针是否为空  
    {  
        evan_auto_ptr->PrintEvan(); 

        Evan* evan_ptr = evan_auto_ptr.get();   // get() 返回裸指针的引用
        evan_ptr->SetKey(2);                    // 通过裸指针访问    
        evan_ptr->PrintEvan();  
  
        (*evan_auto_ptr).SetValue("evan");       // operator* 返回智能指针管理的对象  
        (*evan_auto_ptr).PrintEvan(); 

        evan_auto_ptr.reset(new Evan());        // 赋值一个新的
        evan_auto_ptr->PrintEvan();
    } 
}

3、结果

QQ截图20160708111825.png

4、注意

    a. 切记使用 "operator=",万一真用了,就不要再使用旧的对象了。

    // bug - 赋值问题 : Segmentation fault
    {
        auto_ptr<Evan> evan_auto_ptr1; 
        evan_auto_ptr1 = evan_auto_ptr;         // 原因在这行代码  
        evan_auto_ptr1->PrintEvan();  
        evan_auto_ptr->PrintEvan();             // 崩溃在这行代码 
        // evan_auto_ptr1剥夺了evan_auto_ptr的内存管理所有权,导致evan_auto_ptr1悬空。
    }

    b. release()方法不会释放内存 仅仅只是让出所有权。

    // bug - release问题
    {
        evan_auto_ptr.release();                // 并没有看到析构函数被调用的迹象
        evan_auto_ptr.reset(NULL);              // 正确调用
    }

二、boost::scoped_ptr

1、Boost库

    是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一。安装方法参考:

    A、安装依赖库:

apt-get install mpi-default-dev     #安装mpi库  
apt-get install libicu-dev     #支持正则表达式的UNICODE字符集 
apt-get install python-dev     #需要python的话  
apt-get install libbz2-dev     #如果编译出现错误:bzlib.h: No such file or directory

    B、下载安装

    下载:http://www.boost.org/users/history/version_1_58_0.html 建议查看G++版本,找支持的版本的boost;

    编译安装:解压、进入目录、./bootstrap.sh 然后 ./b2 install 

    C、测试

#include<iostream>  
#include<boost/bind.hpp>  
using namespace std;  
using namespace boost;  
int fun(int x,int y){return x+y;}  
int main(){  
    int m=1;int n=2;  
    cout<<boost::bind(fun,_1,_2)(m,n)<<endl;  
    return 0;  
}
// 结果:3

2、在namespace boost中,添加头文件 #include<boost/smart_ptr.hpp> 即可使用。

3、与auto_ptr类似,scoped_ptr也可管理单个堆内存的对象,但是它独享所有权,因此也就避免了auto_ptr的缺陷。

4、代码

#include <boost/smart_ptr.hpp>
using namespace boost;
void TestBoostScopedPtr() {
    cout << "TestBoostScopedPtr" <<endl;

    scoped_ptr<Evan> evan_scoped_ptr(new Evan(1,"xia") );
    if (evan_scoped_ptr.get()) {
        evan_scoped_ptr->PrintEvan();               // 通过智能指针访问

        Evan* evan_ptr = evan_scoped_ptr.get();     // 获取裸指针
        evan_ptr->PrintEvan();                      // 裸指针访问
        evan_ptr->SetKey(2);

        (*evan_scoped_ptr).PrintEvan();             // operator* 返回智能指针管理的对象

        // evan_scoped_ptr.release();                  // 错误,没有release成员
        // scoped_ptr<Evan> evan_scoped_ptr2;
        // evan_scoped_ptr2 = evan_scoped_ptr;         // 错误,没有赋值
    }
}

5、结果

QQ截图20160711093637.png

6、注意

    scoped_ptr没有release()函数 屏蔽了operator=操作, 因此不会出现auto_ptr中出现的内存泄漏和崩溃问题。 但这也带来了另一个新问题,我们无法复制智能指针。

三、boost::shared_ptr 

1、由于scoped_ptr不允许赋值 拷贝 独享内存的所有权。 这里的shared_ptr是专门解决智能指针的内存所有权共享这个问题的, 其内部使用了引用计数。

2、代码

void TestBoostSharedPtr(shared_ptr<Evan> evan_shared_ptr) {
    evan_shared_ptr->PrintEvan();
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    shared_ptr<Evan> evan_shared_ptr2;
    evan_shared_ptr2 = evan_shared_ptr;
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    cout << "\t used count:" << evan_shared_ptr2.use_count() << endl;
}

void TestBoostSharedPtr() {
    cout << "TestBoostSharedPtr" <<endl;

    shared_ptr<Evan> evan_shared_ptr(new Evan(1, "xia") );
    if (evan_shared_ptr.get() ) {
        evan_shared_ptr->PrintEvan();               // 通过智能指针访问

        Evan* evan_ptr = evan_shared_ptr.get();     // 获取裸指针
        evan_ptr->PrintEvan();                      // 通过裸指针访问
        evan_ptr->SetValue("haha");
        (*evan_shared_ptr).PrintEvan();             // operator* 返回智能指针管理的对象
    }
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    TestBoostSharedPtr(evan_shared_ptr);
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    // evan_shared_ptr.release();                      // 错误,没有release成员
}

3、结果

QQ截图20160711095157.png

4、注意

    shared_ptr可以赋值,但是同样也没有release成员;

四、boost::intrusive_ptr

1、intrusive_ptr是一种插入式智能指针,其内部没有引用计数,需要自己加入引用计数,否则会编译错误。

2、代码

//接口引用计数,示例例子不是线程安全的
class IUnknown
{
    int m_lCountRef;
public:
    IUnknown() : m_lCountRef(0) {}
    virtual ~IUnknown()  {}

    unsigned long AddRef(void)
    {
        ++m_lCountRef;
        return m_lCountRef;
    }

    unsigned long Release(void)
    {
        if (--m_lCountRef==0)
        {
            delete this;
        }
        return m_lCountRef;
    }
protected:
    IUnknown& operator=(const IUnknown&)
    {
        return *this;
    }
private:
    // 禁止复制构造函数
    IUnknown(const IUnknown&);
};

class com_class : public IUnknown
{
public:
    com_class()
    {
        std::cout << "\t com_class::com_class()\n";
    }

    com_class(const com_class& other)
    {
        std::cout << "\t com_class(const com_class& other)\n";
    }

    ~com_class()
    {
        std::cout << "\t com_class::~com_class()\n";
    }

    //定义两个给intrusive_ptr调用函数
    friend void intrusive_ptr_add_ref(IUnknown* p)
    {
        p->AddRef();
    }

    friend void intrusive_ptr_release(IUnknown* p)
    {
        p->Release();
    }
};

void TestBoosTintrusivePtr() {
    cout << "TestBoosTintrusivePtr" <<endl;

    boost::intrusive_ptr<com_class> p1(new com_class());
    boost::intrusive_ptr<com_class> p2(p1);

}

3、结果

QQ截图20160711142913.png

4、注意

    在多线程环境下,对保持引用计数的变量的任何操作都必须同步化。

    intrusive_ptr是一个侵入式的引用计数型指针,它可以用于以下两种情形:

        对内存占用的要求非常严格,要求必须与原始指针一样;

        现存代码已经有了引用计数机制管理的对象;

五、boost::scoped_array 

1、上面介绍的几种智能指针都针对单个对象的内存管理。其实智能指针也可管理数组。boost::scoped_array用于管理动态数组,也是独享所有权的。

2、代码

void TestBoostScopedArray() {
    cout << "TestBoostScopedArray" <<endl;

    scoped_array<Evan> evan_scoped_arr(new Evan[2] );   // 初始化动态数组
    if (evan_scoped_arr.get() ) {
        evan_scoped_arr[0].PrintEvan();                 // 通过智能指针访问

        evan_scoped_arr.get()[1].PrintEvan();           // 通过裸指针访问
        evan_scoped_arr.get()[0].SetKey(2);

        // (*evan_scoped_arr)[0].PrintEvan();           // 没有operator* 
        // evan_scoped_arr[0].release();                // 没有成员release  
  
        // scoped_array<Evan> evan_scoped_arr2;  
        // evan_scoped_arr2 = evan_scoped_arr;           // 禁止拷贝 
    }
}

3、结果

QQ截图20160711102147.png

4、注意

    scoped_array的使用和scoped_ptr大致差不多。都禁止拷贝,独享所有权。但是scoped_array没有重载operator*操作符。

六、boost::shared_array 

1、shared_array 也是管理数组的智能指针,内部使用了引用计数,解决参数传递和拷贝赋值时的问题。

2、代码

void TestBoostSharedArray(shared_array<Evan> evan_shared_arr) {
    cout << "\t used count:" << evan_shared_arr.use_count() << endl;
    shared_array<Evan> evan_shared_arr2;
    evan_shared_arr2 = evan_shared_arr;
    cout << "\t used count:" << evan_shared_arr.use_count() << endl;
    cout << "\t used count:" << evan_shared_arr2.use_count() << endl;
}

void TestBoostSharedArray() {
    cout << "TestBoostSharedArray" <<endl;
    
    shared_array<Evan> evan_shared_arr(new Evan[2] );   // 初始化动态数组
    if (evan_shared_arr.get() ) {
        evan_shared_arr[0].PrintEvan();                 // 通过智能指针访问

        evan_shared_arr.get()[1].PrintEvan();           // 通过裸指针访问
        // (*evan_scoped_arr)[0].PrintEvan();           // 没有operator* 
    }
    cout << "\t used count:" << evan_shared_arr.use_count() << endl;
    TestBoostSharedArray(evan_shared_arr);
    cout << "\t used count:" << evan_shared_arr.use_count() << endl;
}

3、结果

QQ截图20160711103303.png

4、注意

    和数组相关的智能指针都没有实现operator*,所以不能使用*运算符;

    boost库中智能指针都没有release成员;

七、boost::weak_ptr

1、若我们仅仅关心能否使用对象,而不关心内部的引用计数。 boost::weak_ptr 是 boost::shared_ptr 的观察者(Observer)对象, 意味着boost::weak_ptr只对boost::shared_ptr引用,但是并不更新 其引用计数。被观察的shared_ptr失效后,weak_ptr相应也失效。

2、代码

void TestBoostWeakPtr() {
    cout << "TestBoostWeakPtr" <<endl;

    shared_ptr<Evan> evan_shared_ptr(new Evan(1,"xia") );
    if (evan_shared_ptr.get() ) {
        weak_ptr<Evan> evan_weak_ptr;
        cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
        evan_weak_ptr = evan_shared_ptr;
        // evan_weak_ptr->PrintEvan();                     // 没有->
        // evan_weak_ptr.get()->SetKey(2);                 // 没有get()
        // (*evan_weak_ptr).PrintEvan();                   // 没有*
        cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
        shared_ptr<Evan> evan_shared_ptr2;
        evan_shared_ptr2 = evan_weak_ptr.lock();           // 使用前必须转换
        evan_shared_ptr2->PrintEvan(); 
        cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    }
}

3、结果

QQ截图20160711105027.png

4、注意

    它对被 shared_ptr 管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先lock将其转换为 shared_ptr。有点儿类似于void*;

5、应用

    weak_ptr更多用于:在基类中定义一个weak_ptr,用于观察其子类中的shared_ptr。这样基类只要看自己的weak_ptr是否为空就知道子类有没对自己赋值,而不影响子类中shared_ptr的引用计数,从而降低复杂度,更好管理对象。同时weak_ptr也有防止环形引用问题。

八、shared_ptr

1、C++11支持

    为了使用C++11编译,代码中注释了boost部分的内容,并且编译时使用了:

g++ -std=c++11 ./main.cc

2、shared_ptr来源于boost::shared_ptr,思想和用法差不多。

3、代码

void TestSharedPtr(shared_ptr<Evan> evan_shared_ptr) {
    evan_shared_ptr->PrintEvan();
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    shared_ptr<Evan> evan_shared_ptr2;
    evan_shared_ptr2 = evan_shared_ptr;
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    cout << "\t used count:" << evan_shared_ptr2.use_count() << endl;
}

void TestSharedPtr() {
    cout << "TestSharedPtr" <<endl;

    shared_ptr<Evan> evan_shared_ptr(new Evan(1, "xia") );
    if (evan_shared_ptr.get() ) {
        evan_shared_ptr->PrintEvan();               // 通过智能指针访问

        Evan* evan_ptr = evan_shared_ptr.get();     // 获取裸指针
        evan_ptr->PrintEvan();                      // 通过裸指针访问
        evan_ptr->SetValue("haha");

        (*evan_shared_ptr).PrintEvan();             // operator* 返回智能指针管理的对象
    }
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    TestSharedPtr(evan_shared_ptr);
    cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    // evan_shared_ptr.release();                      // 错误,没有release成员
}

4、结果

QQ截图20160711111728.png

5、注意

    同一个shared_ptr被多个线程读,是线程安全的;

    同一个shared_ptr被多个线程写,不是 线程安全的;

    共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。

    shared_ptr指向数组在默认情况下,shared_ptr将调用delete进行内存的释放;当分配内存时使用new[]时,我们需要对应的调用delete[]来释放内存;为了能正确的使用shared_ptr指向一个数组,我们就需要定制一个删除函数。

九、weak_ptr

1、来源于boost::weak_ptr,思想和用法一样;

2、代码

void TestWeakPtr() {
    cout << "TestWeakPtr" <<endl;
    
    shared_ptr<Evan> evan_shared_ptr(new Evan(1, "xia") );
    if (evan_shared_ptr.get() ) {
        weak_ptr<Evan> evan_weak_ptr;
        cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
        evan_weak_ptr = evan_shared_ptr;
        // evan_weak_ptr->PrintEvan();                     // 没有->
        // evan_weak_ptr.get()->SetKey(2);                 // 没有get()
        // (*evan_weak_ptr).PrintEvan();                   // 没有*
        cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
        shared_ptr<Evan> evan_shared_ptr2;
        evan_shared_ptr2 = evan_weak_ptr.lock();           // 使用前必须转换
        evan_shared_ptr2->PrintEvan(); 
        cout << "\t used count:" << evan_shared_ptr.use_count() << endl;
    }
}

3、结果

QQ截图20160711112149.png

十、unique_ptr

1、C++11中的unique_ptr是auto_ptr的替代品,它与auto_ptr一样拥有唯一拥有权的特性,与auto_ptr不一样的是,unique_ptr是没有复制构造函数的,这就防止了一些“悄悄地”丢失所有权的问题发生,如果需要将所有权进行转移,只有在使用者显示的调用std::move之后,才会发生所有权的转移。和boost::scoped_ptr类似。

2、代码

void TestUniquePtr() {
    cout << "TestUniquePtr" <<endl;

    unique_ptr<Evan> evan_unique_ptr(new Evan(1, "xia") );
    if (evan_unique_ptr.get() ) {
        evan_unique_ptr->PrintEvan();               // 智能指针访问

        Evan* evan_ptr = evan_unique_ptr.get();     // 裸指针访问
        evan_ptr->PrintEvan();

        (*evan_unique_ptr).PrintEvan();             // *获取对象访问

        unique_ptr<Evan> evan_unique_ptr2;
        // evan_unique_ptr2 = evan_unique_ptr;         // 错误,没有=
        evan_unique_ptr2 = move(evan_unique_ptr);   // 必须通过move转移,函数传参也一样
        evan_unique_ptr2->PrintEvan();  
        // evan_unique_ptr->PrintEvan();                // 转移所有权后原来的就不能访问了
    }
}

3、结果

QQ截图20160711111100.png

4、注意

    直接传参时,进行值传递时,建立临时变量时,就会出错了,所以需要显示的调用move,转移所有权;而函数的返回值已经进行了move操作,而不用显示的进行调用。

十一、总结

名称管理对象所有权复制(构造)operator=赋值operator*release
C++98auto_ptr单个赋值转让YYYY
boostscoped_ptr单个独享NNYN
shared_ptr 单个共享YYYN
intrusive_ptr单个共享YYYN
scoped_array 动态数组独享NNNN
shared_array 动态数组共享YYNN
weak_ptr单个共享YYYN
C++11shared_ptr单个共享YYYN
weak_ptr单个共享YYYN
unique_ptr单个独享NNYN

源码:https://git.oschina.net/evan-xia/x_1607_CppPtr.git

参考

1、http://blog.csdn.net/u013575812/article/details/51155815

2、http://blog.csdn.net/yhrun/article/details/8099630

3、http://www.jellythink.com/archives/684

4、http://coolshell.cn/articles/7992.html

5、http://www.cnblogs.com/TenosDoIt/p/3456704.html

6、http://blog.csdn.net/callmeback/article/details/7729251

7、http://blog.csdn.net/caimouse/article/details/8631381


转载标明出处:https://blog.evanxia.com/2016/07/824