C/C++编程:智能指针

1059 篇文章 278 订阅

计算机系统中的资源有很多种,内存是我们最常用到的,此外还有文件描述符,socket、操作系统handler,数据库连接等,程序里申请这些资源之后必须及时归坏系统,否则就会产生难以预料的后果

std::unique_ptr<char[]> buffer(new char[1024]);

疑惑

引用与非引用做返回值(shared_ptr)


#include <fstream> // ifstream, ifstream::in
#include <iostream>
#include <fcntl.h>
#include <zconf.h>
#include <memory>
#include <map>


using namespace std;

struct Foo{
    Foo(){printf("%s", "Foo:Foo\n");}
    ~Foo(){printf("%s", "~Foo:Foo\n");}
    void foo(){printf("%s", "====Foo:foo\n");}
};

std::map<std::string , std::shared_ptr<struct Foo>> m_dev_info;

void init(){
    std::shared_ptr<struct Foo> dev_info = make_shared<struct Foo>();
    m_dev_info["aaa"] = dev_info;
}

shared_ptr<struct Foo> &  get_dev_yinyong(const std::string& camera_id){   //引用计数不加1
    return m_dev_info["aaa"];
}

shared_ptr<struct Foo>   get_dev_noyinyong(const std::string& camera_id){  // 引用计数加1
    return m_dev_info["aaa"];
}
int main(){

    init();
    printf("%ld\n", m_dev_info["aaa"].use_count());
    shared_ptr<struct Foo> dev_info;

//    shared_ptr<struct Foo> & i = get_dev_yinyong("aaa");
//    printf("%ld\n", m_dev_info["aaa"].use_count());

    shared_ptr<struct Foo> f = get_dev_noyinyong("aaa");
    printf("%ld\n", m_dev_info["aaa"].use_count());
    getchar();
    return 0;
}



引用&非引用做参数(shared_ptr)


#include <fstream> // ifstream, ifstream::in
#include <iostream>
#include <fcntl.h>
#include <zconf.h>
#include <memory>
#include <map>


using namespace std;

struct Foo{
    Foo(){printf("%s", "Foo:Foo\n");}
    ~Foo(){printf("%s", "~Foo:Foo\n");}
    void foo(){printf("%s", "====Foo:foo\n");}
};

std::map<std::string , std::shared_ptr<struct Foo>> m_dev_info;

void init(){
    std::shared_ptr<struct Foo> dev_info = make_shared<struct Foo>();
    m_dev_info["aaa"] = dev_info;
}

bool  get_dev_info(const std::string& camera_id,  shared_ptr<struct Foo> &infoTag){   //引用计数+1
    infoTag = m_dev_info["aaa"];
    return true;
}

bool  get_dev_info1(const std::string& camera_id,  shared_ptr<struct Foo> infoTag){  // 引用计数不加1
    infoTag = m_dev_info["aaa"];
    return true;
}
int main(){

    init();
    printf("%ld\n", m_dev_info["aaa"].use_count());
    shared_ptr<struct Foo> dev_info;

    get_dev_info("aaa", dev_info);
    printf("%ld\n", m_dev_info["aaa"].use_count());
    getchar();
    return 0;
}



RAII机制

为了管理内存等资源,C++程序员中通常采用RAII机制(资源获取即初始化),在类的构造函数中申请资源,然后使用,最后在析构函数中释放资源

  • 如果对象使用声明在栈上构建的,那么当对象离开作用域时对象会自动销毁从而调用析构函数释放资源
  • 如果对象是用new在堆上创建的,那么必须由程序员手动的调用delete操作符销毁它才能释放资源。如果忘记释放,就会存在资源泄漏的隐患;还有种可能是因为某些意外导致程序未能执行delete语句,也会造成内存丢失。
    在这里插入图片描述

显式内存管理

实际编写C/C++程序的时候,常会遇到比如程序允许是忽然退出,或者占用的内存越来越多,最后不得不定期重启的一些现象。这些问题的源头可以追溯到C/C++中的显式堆内存管理上(原因一般是没有正确的处理堆内存的分配和是否)。问题一般由三个:

  • 野指针:一些内存单元已经被释放,之前指向它的指针却还在被使用。这些内存有可能被运行时系统重新分配给了程序使用,从而导致无法预测的错误。
  • 重复释放:程序试图去释放已经释放过的内存单元,或者释放已经被重新分配过的内存单元,就会导致重复释放错误。通常重复释放内存会导致C/C++运行时系统打印出大量错误和诊断信息
  • 内存泄漏:不需要再使用的内存单元如果没有被释放就会导致内存泄漏。如果程序不断地重复进行这类操作,将会导致内存占用剧增。

虽然显式的管理内存在性能上有一些优势,但是也非常容器出错。因此一个理念就是编程语言应该提供更好的机制,让程序员摆脱内存管理的细节。在C++中,一个这样的机制就是标准库中的智能指针。进一步的,标准库还提供了所谓”最小垃圾回收“支持。

智能指针

智能指针可以让对象在退出作用域时(不管是正常离开而是异常离开),调用delete来析构在堆上动态分配的对象

boost.smart_ptr库提供了六种智能指针:scoped_ptrshared_ptrweak_ptrinstrusive_ptrscoped_arrayshared_array

  • 它们都是轻量级的对象,速度和原始指针相差几无,都是异常安全(exception safe)的
  • 而且对于所指向的类型T也仅有一个很小很合理的要求:类型T的析构函数不能抛出异常
  • scoped_arrayshared_array很少用。

C++标准库提供的智能指针:weak_ptrshared_ptrunique_ptr。它们是基于boost的smart_ptr库的

boost库的只能指针都位于名字空间boost,需要包含头文件#include <boost/smart_ptr.hpp>,即使:

#include <boost/smart_ptr.hpp>
using namespace boost;

补充:

  • auto_ptr:最早的智能指针, 是C++98标准化引入的
  • auto_ptr虽说简单,但使用起来却到处是坑,以至于大家都不提倡使用(无论在什么情况下都不要使用)
  • std::auto_ptr已在标准库中声明为飞起,现在应该使用新的智能指针std::unique_ptr

scoped_ptr

  • 这个智能指针只能在本作用域里面使用,不能被转让
  • 它包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确的删除。

类摘要

在这里插入图片描述在这里插入图片描述

操作函数

  • scoped_ptr的构造函数
    • 接受一个类型为T*的指针p,创建出一个scoped_ptr对象,并在内部参数保存指针参数p。p必须满足下面两种要求:
      • p要么是一个new表达式动态分配的结果
      • p要么是一个空指针nullptr
    • 当scoped_ptr对象的生命期结束时,析构函数对使用delete操作符自动销毁所保存的对象,从而正确的回收资源
  • scoped_ptr把拷贝构造函数和赋值函数都声明为私有的,禁止拷贝或者赋值,从而保证了被它管理的指针不能转让所有权,只能在scoped_ptr声明的作用域内使用
  • reset: 重置scoped_ptr, 它删除了原来保存的指针,再保存新的指针值p。
    • 如果p是空指针,那么scoped_ptr将不再持有任何指针
    • 一般情况下,reset不应该被调用,因为它违背了scoped_ptr的本意–》资源应该一直由scoped+pty自己自动管理。

实践

例子:

#include <boost/smart_ptr.hpp>
using namespace std;
using namespace boost;


class X
{
public:
    X(){
        cout <<  "X ..." << endl;
    }
    ~X(){
        cout <<  "~X ..." << endl;
    }
};
int main(int argc,char *argv[]){
    cout <<  "Entering main ..." << endl;

    {
        boost::scoped_ptr<X> pp( new X);

        //boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移
    }

    cout <<  "Exiting main ..." << endl;
    return 0;
}

在这里插入图片描述

例子

#include <boost/smart_ptr.hpp>
using namespace std;
using namespace boost;


int main(int argc,char *argv[]){
    scoped_ptr<string> scopedPtr(new string("text"));  // scoped_ptr是一个对象而不是指针,没有办法delete的。

    assert(scopedPtr != nullptr);

    cout << *scopedPtr << endl; // text   : * 取出字符串的内容
    cout << scopedPtr->size() << endl; //4  : -> 字符串的长度

    scopedPtr.reset();

    assert(scopedPtr == nullptr);

    return 0;
}

源码阅读

template<class T> class scoped_ptr // noncopyable
{
private:

    T * px; // 原始指针

	// 不能拷贝/赋值:保证了被它管理的指针不能被转让所有权
    scoped_ptr(scoped_ptr const &);  // 拷贝构造函数私有化--->不能拷贝
    scoped_ptr & operator=(scoped_ptr const &);  // 赋值函数私有化 ---> 不能赋值

    typedef scoped_ptr<T> this_type;

    void operator==( scoped_ptr const& ) const;  // 相等函数私有化
    void operator!=( scoped_ptr const& ) const;  // 不等函数私有化

public:

    typedef T element_type;

  /*
    接受一个类型为T*的指针p,创建出一个scoped_ptr对象,并在内部参数保存指针参数p
    p必须是一个new表达式动态分配的结果,或者一个空指针nullptr
    当scoped_ptr对象的生命期结束时,析构函数对使用delete操作符自动销毁所保存的对象,从而正确的回收资源
    */
    explicit scoped_ptr( T * p = 0 ): px( p ) // never throws    // 显式构造函数
    {
  
    }  

    explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT : px( p.release() )
    {
    }

    ~scoped_ptr() // never throws   // 析构函数
    {
        boost::checked_delete( px );
    }

	// 重置scoped_ptr, 它删除了原来保存的指针,再保存新的指针值p。
	// 如果p是空指针,那么scoped_ptr将不再持有任何指针
	// 一般情况下,reset不应该被调用,因为它违背了scoped_ptr的本意--》资源应该一直由scoped+pty自己自动管理。
    void reset(T * p = 0) // never throws  // 重置智能指针
    {
        BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
        this_type(p).swap(*this);
    }

//	* 和->重载的意义: 模仿被代理的指针的行为
    T & operator*() const // never throws  //操作符重载
    {
        BOOST_ASSERT( px != 0 );  // BOOST_ASSERT仅工作再debug模式下
        return *px;
    }
    T * operator->() const // never throws  //操作符重载
    {
        BOOST_ASSERT( px != 0 );
        return px;
    }

// 用在某些要求必须是原始指针的场景,但是使用时必须小写,这将使原始指针脱离scoped_ptr的控制,不能对这个指针delete
    T * get() const BOOST_NOEXCEPT  // 获取原始指针
    {
        return px;
    }

// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp>
  
    void swap(scoped_ptr & b) BOOST_NOEXCEPT 
    {
        T * tmp = b.px;
        b.px = px;
        px = tmp;
    }
};

unique_ptr

std::unique_pt形如起名,是一种独占的智能指针,与所指对象的内存绑定紧密, 禁止其他智能指针与其共享同一个对象,从而导致代码的安全:

    auto pointer = std::make_unique<int>(10); //C++14引入
    auto p1 = pointer; // 非法

既然是独占,也就是说不可复制。但是,我们可以利用std::move将其转移给其他unique_ptr,一旦被转移,就不再拥有,除非被显示的归还。

例如:

#include <iostream>
#include <memory>

struct Foo{
    Foo(){printf("%s", "Foo:Foo\n");}
    ~Foo(){printf("%s", "~Foo:Foo\n");}
    void foo(){printf("%s", "====Foo:foo\n");}
};

void f(const Foo &){
    printf(" f(const Foo &)\n");
}


int main(){
    std::unique_ptr<Foo> p1(std::make_unique<Foo>());
    // p1不空,输出
    if (p1){
        p1->foo();
    }

    {
        std::unique_ptr<Foo> p2(std::move(p1));
        f(*p2); //  f(const Foo &)

        if(p2){
            p2->foo(); // ====Foo:foo
        }
        if (p1){
            p1->foo(); // std::move给了p2,因此无输出
        }

        p1 = std::move(p2);
        if(p2){
            p2->foo(); // std::move给了p1,因此无输出
        }
        if (p1){
            p1->foo(); // ====Foo:foo
        }

        p2 = std::move(p1);
        printf("p2被销毁\n");
    }

    if (p1){
        p1->foo(); // ====Foo:foo
    }
    return 0;
}

从实现上来讲,unique_ptr是一个删除了拷贝构造函数、保留了移动构造函数的指针封装类型。程序员仅可以使用右值对unique_ptr对象进行构造,而且一旦构造成功,右值对象中的指针即被“窃取”,因此,该右值对象马上失去了对指针的“所有权”

shared_ptr

  • shared_ptr是一个最像指针的智能支持,被c++11标准收入
  • shared_ptr是一种智能指针,它能够记录多少个shared_ptr共同指向同一个对象,从而消除显式调用的delete,当引用计数变成0时就会将对象自动删除
  • shared_ptrscoped_ptr一样,包装了new操作符在堆上分配的动态对象,但它实现的是引用计数性的智能指针,可以被自由的拷贝和赋值,在任意地方共享它,当没有代码使用(引用计数为0)它时才被包装的动态分配的对象释放
  • 因为使用std::shared_ptr仍然需要new来调用,这使得代码出现了不对称,std::make_shared就能够消除显式的调用new,所以std::make_shared会分配创建传入参数中的对象,并返回这个对象类型的std::shared_ptr指针

实践

#include <memory>
#include <assert.h>
#include <map>
#include <iostream>

using namespace  std;
class Shape{
public:
    Shape() {
        cout<<"Base::Draw()"<<endl;
    }
    ~Shape() {
        cout<<"Base::Erase()"<<endl;
    }
};

class Polygon:public Shape{
public:
    Polygon() {cout<<"Polygon::Draw()"<<endl;}
    ~Polygon() {cout<<"Polygon Erase()"<<endl;}
};

class Rectangle:public Polygon{
public:
    Rectangle() {cout<<"Rectangle::Draw()"<<endl;}
    ~Rectangle() {cout<<"Rectangle Erase()"<<endl;}
};

初始化

{
    //一般的初始化方式
	shared_ptr<string> pint_s(new string("normal usage!"));
    cout<<*pint_s<<endl;

    //推荐的安全的初始化方式
    shared_ptr<string> pint_s1 = make_shared<string>("safe uage!");
    cout<<*pint_s1<<endl;
}

在这里插入图片描述

    {
        //一般的初始化方式(不推荐)
        shared_ptr<Shape> pint(new Shape());
    }
    {
        //推荐的安全的初始化方式
        shared_ptr<Shape> pint1 = make_shared<Shape>();
    }
    {
        // 变量类型太长,可以重命名(推荐)
        typedef shared_ptr<Shape> sp_t;
        sp_t sp2 = make_shared<Shape>();
    }

在这里插入图片描述

 typedef shared_ptr<Shape> sp_t;
 sp_t sp1 = make_shared<Polygon>();

在这里插入图片描述

托管指针

int main6()
{
    shared_ptr<Shape> sp1= make_shared<Shape>();
} // Base::Draw() ---> 由sp1托管
    shared_ptr<Shape> sp2(sp1);  // 同时交由sp2托管
    shared_ptr<Shape> sp3;
    sp3 = sp1; // 同时交由sp3托管


    // 注意,不能用下面的方式使得两个 shared_ptr 对象托管同一个指针:

  /*  Shape* p = new Shape;
    shared_ptr <Shape> sp4(p), sp5(p);

    sp4 和 sp5 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp5 无法知道 p 已经被 sp4 托管过)。这样,当 sp4 消亡时要析构 p,sp5 消亡时要再次析构 p,这会导致程序崩溃。
*/

    return 0;
}

智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空

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

int main()
{
    /*---------空指针------------*/
    shared_ptr<string> p1;
    if(!p1)                   //!默认初始化的智能指针中保存着一个空指针!并不是""空字符串
    cout<<"p1==NULL"<<endl;
    /*---------初始化------------*/
    shared_ptr<string> p2(new string);
    if(p2&&p2->empty()){ //!需要注意的时empty时属于string的成员函数。
        *p2="helloworld";
        cout<<*p2<<endl;
    }


 	shared_ptr<int> spi(new int); // 一个int类型的shared_ptr
    assert(spi);                    // bool类型转换判断指针的有效性
    *spi = 256;                     


    shared_ptr<std::string>  sps(new std::string("smart"));
    assert(sps->size() == 5);

//    shared_ptr<int> pa = new int(1);//!error:不允许以暴露裸漏的指针进行赋值操作。
    //一般的初始化方式

   shared_ptr<std::exception> sp1(new std::bad_exception());

    auto sp2 = dynamic_pointer_cast<std::bad_exception>(sp1);
    auto sp3 = static_pointer_cast<std::exception>(sp2);
    assert(sp3 == sp1);
    

/**********************不推荐***********************/
    int * p = new int(32);
    shared_ptr<int> pp(p);
    cout<<*pp<<endl;

    /*意外的情况*/
//    delete p;               //!不小心把delete掉了。
//    cout<<*pp<<endl;·       //!pp也不再有效。
}

关于get()函数

智能指针定义了一个名为get的函数,它返回一个内置指针,指向智能指针的管理的对象。此函数设置的初衷是当我们向不能使用智能指针的代码传递一个内置指针。使用get返回指针的代码不能delete此指针。

 useShared_ptr(p1.get());
//    delePointer(p1.get());        //!error:

也就是说,std::shared_ptr可以通过get()方法来获取原始指针,通过reset()来减少一个引用计数,并通过使用use_count来查看一个对象的引用计数

#include <iostream>
#include <memory>



int main(){
    auto pointer = std::make_shared<int>(10);
    auto pointer2 = pointer; // 引用计数 +1
    auto pointer3 = pointer; // 引用计数 +1
    int *p = pointer.get(); // 这样不会增加引用计数
    std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
    std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
    std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3
    pointer2.reset();
    std::cout << "reset pointer2:" << std::endl;
    std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
    std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset
    std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
    pointer3.reset();
    std::cout << "reset pointer3:" << std::endl;
    std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
    std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
    std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;

    return 0;
}

再次声明:

  • get用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get。
  • 特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值!

关于make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回此对象的shared_ptr。与智能指针一样,make_shared也定义在头文件memory中。

#include <iostream>
using namespace std;

int main()
{
    shared_ptr<int> p3 = make_shared<int>(42);
    cout<<*p3<<endl;

    shared_ptr<string> pstr = make_shared<string>("99999");
    cout<<*pstr<<endl;

    shared_ptr<int> pint = make_shared<int>(); //!默认初始化为 0
    cout<<*pint<<endl;

    auto pau = make_shared<string>("auto");    //!更简单,更常用的方式。
    cout<<*pau<<endl;
}

使用make_shared用其参数来构造给定类型的对象;传递的参数必须能够与该类型的某个构造函数相匹配。
通常我们用auto来定义一个对象来保存make_shared的结果,这种方式更为简单。

shared_ptr的拷贝和赋值

当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象:

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


int main()
{
    auto p = make_shared<int>(42); //!p指向的对象只有p一个引用者。
    cout<<p.use_count()<<endl;  // 1  //返回与p共享对象的智能指针数量;可能很慢,主要用于调试。
    cout<<p.unique()<<endl;   // 若p.use_count()为1,则返回 true;否则返回 false
    auto q(p);                     //!p和q指向相同的对象,此对象有两个引用者。
    cout<<p.use_count()<<endl;  // 2
    cout<<q.use_count()<<endl;  // 2
    return 0;
}

shared_ptr作返回值

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

shared_ptr<string> factory(const char * p){
    return make_shared<string>(p);
}

void user_factory(){
    shared_ptr<string> p = factory("helloworld"); // p和return返回的时同一个对象
    cout<<p.use_count() <<endl; //1
    cout<<*p<<endl; //!离开作用域时,p引用的对象被销毁。
}


shared_ptr<string> return_share_ptr()
{
    shared_ptr<string> p = factory("helloworld");
    cout<<*p<<endl;
    return p; //!返回p时,引用计数进行了递增操作。
} //!p离开了作用域,但他指向的内存不会被释放掉。



int main()
{
    user_factory();
    auto p = return_share_ptr();
    cout<<p.use_count()<<endl; // 1
    return 0;
}

容器中的shared_ptr

对于一块内存,shared_ptr类保证了只要有share_ptr对象引用它,他就不会被释放掉。由于这个特性,保证shared_ptr在不用之后不再保留就非常重要了,通常这个过程能够自动执行而不需要人工干预。有一种例外是我们将shared_ptr放在了容器中。所以永远不要忘记erease不用的shared_ptr

#include <iostream>
#include <memory>
#include <list>
#include <map>
using namespace std;


int main()
{
    map<string, shared_ptr<string>> mapptr;
    mapptr["aaaa"] = make_shared<string>("11111");
    mapptr["bbbb"] = make_shared<string>("2222");
    mapptr.erase("aaaa");

    std::cout << mapptr["aaaa"] << "\t";
    std::cout << mapptr["bbbb"] << "\n";

    list<shared_ptr<string>>pstrList;
    pstrList.push_back(make_shared<string>("1111"));
    pstrList.push_back(make_shared<string>("2222"));
    pstrList.push_back(make_shared<string>("3333"));
    pstrList.push_back(make_shared<string>("4444"));



    for(auto itr = pstrList.begin() ;itr!=pstrList.end();){
        // erase() 接受一个 iterator 作为入参,可从list或vector中删除该 iterator 所指向的元素。但同时传入的该 iterator 即失效!!
        if(**itr == "3333"){
            itr = pstrList.erase(itr);
        }else{
            itr++;
        }
    }

    cout<<"-------------after remove------------"<<endl;
    for(auto p:pstrList)
    {
        cout<<*p<<endl;
    }
    return 0;
}

shared_ptr对象的销毁

  1. 管理动态数组

默认情况下,shared_ptr指向的动态的内存是使用delete来删除的。这和我们手动去调用delete然后调用对象内部的析构函数是一样的。与unique_ptr不同,shared_ptr不直接管理动态数组。如果希望使用shared_ptr管理一个动态数组,必须提供自定义的删除器来替代delete 。

状态共享

使用shared_ptr在一个常见的原因是允许多个多个对象共享相同的状态,而非多个对象独立的拷贝!

#include <iostream>
#include <memory>
#include <list>
#include <map>
using namespace std;


void copyCase()
{
    list<string> v1({"1","b","d"});
    list<string> v2 = v1;        //!v1==v2占用两段内存
    v1.emplace_back("cc");            //!v1!=v2
    cout<< "copyCase : "<<v1.size() << "\t" << v2.size() <<endl;
} //v1和v2分属两个不同的对象,一个改变不会影响的状态。  // copyCase : 4	3

void shareCase()
{
    shared_ptr<list<string>> v1 = make_shared<list<string>>(2,"bb");
    shared_ptr<list<string>> v2 = v1;

    (*v1).emplace_back("c2c");

    cout<<"shareCase : " <<v1->size() << "\t" << v2->size() <<endl;
} //v1和v2属于一个对象的两个引用,有引用计数为证,其内容的改变是统一的。  shareCase : 3	3
int main()
{
    copyCase();
    cout<<"++++++++++++++++"<<endl;
    shareCase();
}

智能指针与异常

异常发生后,常规的动态内存常常不能正确释放。但是如果使用智能指针,即程序过早结束,智能指针也能确保在内存不需要时将其释放:

void f()
{
     shared_ptr<int>sp(new int(42)) ; 
}

函数的推出,要么有两种情况,正常处理结束或者发生了异常,无论哪种情况,局部对象都会被销毁。在上面的程序中,sp是一个shared_ptr,因此sp销毁时会检查引用计数。在此例中,sp是指向这块内存的唯一指针。所以会被释放掉。

与之相对的,当发生异常时,我们直接管理的内存时不会自动释放的,如果使用内置指针管理内存,且在new之后对应的delet之前发生异常,则内存不会释放。

 void f()
{
    int *p = new int(42);
    //code//!异常抛出,且没有在f()中被捕获。
    delete p;      
}

如果在new和delete之间发生异常,且异常未在f()中捕获,则内存就永远不会被释放了。

源码

template<class T> class shared_ptr
{
private:

    // Borland 5.5.1 specific workaround
    typedef shared_ptr<T> this_type;

public:

    typedef typename boost::detail::sp_element< T >::type element_type;

    shared_ptr() BOOST_NOEXCEPT : px( 0 ), pn() // never throws in 1.30+
    {
    }

#if !defined( BOOST_NO_CXX11_NULLPTR )

    shared_ptr( boost::detail::sp_nullptr_t ) BOOST_NOEXCEPT : px( 0 ), pn() // never throws
    {
    }

#endif

    template<class Y>
    explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete
    {
        boost::detail::sp_pointer_construct( this, p, pn );
    }

    //
    // Requirements: D's copy constructor must not throw
    //
    // shared_ptr will release p by calling d(p)
    //

    template<class Y, class D> shared_ptr( Y * p, D d ): px( p ), pn( p, d )
    {
        boost::detail::sp_deleter_construct( this, p );
    }

#if !defined( BOOST_NO_CXX11_NULLPTR )

    template<class D> shared_ptr( boost::detail::sp_nullptr_t p, D d ): px( p ), pn( p, d )
    {
    }

#endif

    // As above, but with allocator. A's copy constructor shall not throw.

    template<class Y, class D, class A> shared_ptr( Y * p, D d, A a ): px( p ), pn( p, d, a )
    {
        boost::detail::sp_deleter_construct( this, p );
    }

#if !defined( BOOST_NO_CXX11_NULLPTR )

    template<class D, class A> shared_ptr( boost::detail::sp_nullptr_t p, D d, A a ): px( p ), pn( p, d, a )
    {
    }

#endif

//  generated copy constructor, destructor are fine...

#if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES )

// ... except in C++0x, move disables the implicit copy

    shared_ptr( shared_ptr const & r ) BOOST_NOEXCEPT : px( r.px ), pn( r.pn )
    {
    }

#endif

    template<class Y>
    explicit shared_ptr( weak_ptr<Y> const & r ): pn( r.pn ) // may throw
    {
        boost::detail::sp_assert_convertible< Y, T >();

        // it is now safe to copy r.px, as pn(r.pn) did not throw
        px = r.px;
    }

    template<class Y>
    shared_ptr( weak_ptr<Y> const & r, boost::detail::sp_nothrow_tag )
    BOOST_NOEXCEPT : px( 0 ), pn( r.pn, boost::detail::sp_nothrow_tag() )
    {
        if( !pn.empty() )
        {
            px = r.px;
        }
    }

    template<class Y>
#if !defined( BOOST_SP_NO_SP_CONVERTIBLE )

    shared_ptr( shared_ptr<Y> const & r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() )

#else

    shared_ptr( shared_ptr<Y> const & r )

#endif
    BOOST_NOEXCEPT : px( r.px ), pn( r.pn )
    {
        boost::detail::sp_assert_convertible< Y, T >();
    }

    // aliasing
    template< class Y >
    shared_ptr( shared_ptr<Y> const & r, element_type * p ) BOOST_NOEXCEPT : px( p ), pn( r.pn )
    {
    }

#ifndef BOOST_NO_AUTO_PTR

    template<class Y>
    explicit shared_ptr( std::auto_ptr<Y> & r ): px(r.get()), pn()
    {
        boost::detail::sp_assert_convertible< Y, T >();

        Y * tmp = r.get();
        pn = boost::detail::shared_count( r );

        boost::detail::sp_deleter_construct( this, tmp );
    }

#if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES )

    template<class Y>
    shared_ptr( std::auto_ptr<Y> && r ): px(r.get()), pn()
    {
        boost::detail::sp_assert_convertible< Y, T >();

        Y * tmp = r.get();
        pn = boost::detail::shared_count( r );

        boost::detail::sp_deleter_construct( this, tmp );
    }

#elif !defined( BOOST_NO_SFINAE ) && !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION )

    template<class Ap>
    explicit shared_ptr( Ap r, typename boost::detail::sp_enable_if_auto_ptr<Ap, int>::type = 0 ): px( r.get() ), pn()
    {
        typedef typename Ap::element_type Y;

        boost::detail::sp_assert_convertible< Y, T >();

        Y * tmp = r.get();
        pn = boost::detail::shared_count( r );

        boost::detail::sp_deleter_construct( this, tmp );
    }

#endif // BOOST_NO_SFINAE, BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION

#endif // BOOST_NO_AUTO_PTR

#if !defined( BOOST_NO_CXX11_SMART_PTR )

    template< class Y, class D >
    shared_ptr( std::unique_ptr< Y, D > && r ): px( r.get() ), pn()
    {
        boost::detail::sp_assert_convertible< Y, T >();

        typename std::unique_ptr< Y, D >::pointer tmp = r.get();
        pn = boost::detail::shared_count( r );

        boost::detail::sp_deleter_construct( this, tmp );
    }

#endif

    // assignment

    shared_ptr & operator=( shared_ptr const & r ) BOOST_NOEXCEPT
    {
        this_type(r).swap(*this);
        return *this;
    }

#if !defined(BOOST_MSVC) || (BOOST_MSVC >= 1400)

    template<class Y>
    shared_ptr & operator=(shared_ptr<Y> const & r) BOOST_NOEXCEPT
    {
        this_type(r).swap(*this);
        return *this;
    }

#endif

#ifndef BOOST_NO_AUTO_PTR

    template<class Y>
    shared_ptr & operator=( std::auto_ptr<Y> & r )
    {
        this_type( r ).swap( *this );
        return *this;
    }

#if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES )

    template<class Y>
    shared_ptr & operator=( std::auto_ptr<Y> && r )
    {
        this_type( static_cast< std::auto_ptr<Y> && >( r ) ).swap( *this );
        return *this;
    }

#elif !defined( BOOST_NO_SFINAE ) && !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION )

    template<class Ap>
    typename boost::detail::sp_enable_if_auto_ptr< Ap, shared_ptr & >::type operator=( Ap r )
    {
        this_type( r ).swap( *this );
        return *this;
    }

#endif // BOOST_NO_SFINAE, BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION

#endif // BOOST_NO_AUTO_PTR

#if !defined( BOOST_NO_CXX11_SMART_PTR )

    template<class Y, class D>
    shared_ptr & operator=( std::unique_ptr<Y, D> && r )
    {
        this_type( static_cast< std::unique_ptr<Y, D> && >( r ) ).swap(*this);
        return *this;
    }

#endif

// Move support

#if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES )

    shared_ptr( shared_ptr && r ) BOOST_NOEXCEPT : px( r.px ), pn()
    {
        pn.swap( r.pn );
        r.px = 0;
    }

    template<class Y>
#if !defined( BOOST_SP_NO_SP_CONVERTIBLE )

    shared_ptr( shared_ptr<Y> && r, typename boost::detail::sp_enable_if_convertible<Y,T>::type = boost::detail::sp_empty() )

#else

    shared_ptr( shared_ptr<Y> && r )

#endif
    BOOST_NOEXCEPT : px( r.px ), pn()
    {
        boost::detail::sp_assert_convertible< Y, T >();

        pn.swap( r.pn );
        r.px = 0;
    }

    shared_ptr & operator=( shared_ptr && r ) BOOST_NOEXCEPT
    {
        this_type( static_cast< shared_ptr && >( r ) ).swap( *this );
        return *this;
    }

    template<class Y>
    shared_ptr & operator=( shared_ptr<Y> && r ) BOOST_NOEXCEPT
    {
        this_type( static_cast< shared_ptr<Y> && >( r ) ).swap( *this );
        return *this;
    }

#endif

#if !defined( BOOST_NO_CXX11_NULLPTR )

    shared_ptr & operator=( boost::detail::sp_nullptr_t ) BOOST_NOEXCEPT // never throws
    {
        this_type().swap(*this);
        return *this;
    }

#endif

    void reset() BOOST_NOEXCEPT // never throws in 1.30+
    {
        this_type().swap(*this);
    }

    template<class Y> void reset( Y * p ) // Y must be complete
    {
        BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
        this_type( p ).swap( *this );
    }

    template<class Y, class D> void reset( Y * p, D d )
    {
        this_type( p, d ).swap( *this );
    }

    template<class Y, class D, class A> void reset( Y * p, D d, A a )
    {
        this_type( p, d, a ).swap( *this );
    }

    template<class Y> void reset( shared_ptr<Y> const & r, element_type * p )
    {
        this_type( r, p ).swap( *this );
    }
    
    // never throws (but has a BOOST_ASSERT in it, so not marked with BOOST_NOEXCEPT)
    typename boost::detail::sp_dereference< T >::type operator* () const
    {
        BOOST_ASSERT( px != 0 );
        return *px;
    }
    
    // never throws (but has a BOOST_ASSERT in it, so not marked with BOOST_NOEXCEPT)
    typename boost::detail::sp_member_access< T >::type operator-> () const 
    {
        BOOST_ASSERT( px != 0 );
        return px;
    }
    
    // never throws (but has a BOOST_ASSERT in it, so not marked with BOOST_NOEXCEPT)
    typename boost::detail::sp_array_access< T >::type operator[] ( std::ptrdiff_t i ) const
    {
        BOOST_ASSERT( px != 0 );
        BOOST_ASSERT( i >= 0 && ( i < boost::detail::sp_extent< T >::value || boost::detail::sp_extent< T >::value == 0 ) );

        return px[ i ];
    }

    element_type * get() const BOOST_NOEXCEPT
    {
        return px;
    }

// implicit conversion to "bool"
#include <boost/smart_ptr/detail/operator_bool.hpp>

    bool unique() const BOOST_NOEXCEPT
    {
        return pn.unique();
    }

    long use_count() const BOOST_NOEXCEPT
    {
        return pn.use_count();
    }

    void swap( shared_ptr & other ) BOOST_NOEXCEPT
    {
        std::swap(px, other.px);
        pn.swap(other.pn);
    }

    template<class Y> bool owner_before( shared_ptr<Y> const & rhs ) const BOOST_NOEXCEPT
    {
        return pn < rhs.pn;
    }

    template<class Y> bool owner_before( weak_ptr<Y> const & rhs ) const BOOST_NOEXCEPT
    {
        return pn < rhs.pn;
    }

    void * _internal_get_deleter( boost::detail::sp_typeinfo const & ti ) const BOOST_NOEXCEPT
    {
        return pn.get_deleter( ti );
    }

    void * _internal_get_untyped_deleter() const BOOST_NOEXCEPT
    {
        return pn.get_untyped_deleter();
    }

    bool _internal_equiv( shared_ptr const & r ) const BOOST_NOEXCEPT
    {
        return px == r.px && pn == r.pn;
    }

// Tasteless as this may seem, making all members public allows member templates
// to work in the absence of member template friends. (Matthew Langston)

#ifndef BOOST_NO_MEMBER_TEMPLATE_FRIENDS

private:

    template<class Y> friend class shared_ptr;
    template<class Y> friend class weak_ptr;


#endif

    element_type * px;                 // contained pointer
    boost::detail::shared_count pn;    // reference counter

};  // shared_ptr

weak_ptr

weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator *->。它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况

std::shared_ptr还是会存在着资源无法释放的问题,如下:

#include <iostream>
#include <memory>
struct A;
struct B;
struct A {
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
struct B {
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;
}

运行结果是A,B都不会销毁,这是因为a, b内部的pointer同时又引用了a, b,这使得a, b的引用计数均变成了2,而离开作用域时,却只能造成这块区域的引用计数减1,这样就导致了a, b对象指向的内存区域引用计数不为0,而外部已经没有办法找到这块区域了,也就造成了内存泄漏,如下图:
在这里插入图片描述
解决这个问题的办法是使用弱引用指针std::weak_pttr,std::weak_ptr是一种弱引用(std::shared_ptr可以看成是强引用)。弱引用不会引起引用计数增加,当换用弱引用的时候,最终的释放流程如下:
在这里插入图片描述
在上图中,最后一步只剩下B,而B并没有任何智能指针引用它,因此这块内存资源也会被释放。

std::weak_ptr没有*运算符和->运算符,所以不能够对资源进行操作,它的唯一作用就是用于检查std::shared_ptr是否存在,其expired()方法能在资源未被释放时,返回false,否则返回true

用法

weak_ptr被设计为与shared_ptr协同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr`没有共享资源,它的构造不会引起指针引用计数的增加或者减少,它只是一个静静的观察者

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值