• c++中的智能指针详解


    智能指针是行为类似于指针的类对象,但是这种对象还有其他的功能。一般用于帮助管理动态内存。

    我们需要知道,我们每次在对象中new出一块内存空间都需要在对象销毁时delete掉,否则就会造成内存泄露,这些工作是程序员自己完成的,也就是说内存分配的任务交给了程序员,万一哪天程序员忘记了,那可能会造成程序崩溃或者服务器宕机out of memery。我们看下面的程序。

    void func(string str){
    	string *ps = new string(str);
    	
    	if (error()) {
    		throw exception();
    	}
    	str = *ps;
    	delete ps;
    	return ;
    }
    

    一旦抛出异常,delete不被执行,也将造成内存泄露。我们可以在抛出异常之前执行delete,但是可以有更巧妙的方法。

    当func()这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将从栈内存中删除——因此指针 ps占据的内存将被释放。如果ps 指向的内存也被释放,那该有多好啊。如果ps有一个析构函数,该析构函数将在ps过期时释放它指向的内存。因此,ps 的问题在于,它只是一个常规指针,不是有析构函数的类对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是 auto_ptr、unique_ptr和 shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案。然而,虽然 auto_ptr被摒弃,但它已使用了多年;同时,如果您的编译器不支持其他两种解决方案,auto_ptr将是唯一的选择。

    使用智能指针

    这三个智能指针模板(auto ptr、 unique ptr和 shared ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete 来释放内存。因此,如果将ew返回的地址赋给这些对象,将无需记住稍后释放这些内存,在智能指针过期时,这些内存将自动被释放。实际上还有一种智能指针weak_ptr,但是我们不讨论这个智能指针。

    要创建智能指针,必须包含头文件memory,该文件模板定义,然后使用常规的模板语法来实例化所需类型的指针,例如,模板auto_ptr包含如下的构造函数

    template <class X> class suto_ptr {
    public:
    	explicit auto_ptr(X *p = 0) throw();
    }
    

    throw()意味着构造函数不会引发异常,与auto_ptr一样,throw()也被抛弃

    auto_ptr<double> pd(new double);
    auto_ptr<string> ps(new string);
    

    new double是new返回的指针,指向新分配的内存块,他是构造函数auto_ptr的参数,即对应原型中形参p的实参,同样,new string也是构造函数的实参,其他两种智能指针使用同样的语法

    unique_ptr pd(new double);
    shared_ptr pd(new double);
    

    所以我们可以修改上面的程序

    #include 
    
    void func(string str){
    	std::auto_ptr ps(new string(str));
    	
    	if (error()) {
    		throw exception();
    	}
    	str = *ps;
    	return ;
    }
    

    每个智能指针都在一个代码块内,离开代码块,智能指针将会过期。

    所有的智能指针类都有一个explicit构造函数,该构造函数将指针作为参数,因此不需要自动将指针转换为智能指针对象

    shared_ptr pd;
    double *p_reg = new double;
    
    pd = p_reg;                      // 不允许
    pd = shared_ptr(p_reg);  // 允许
    
    shared_ptr pshared = p_reg // 不允许
    shared_ptr pshared(p_reg)  // 允许
    

    由于智能指针模板类的定义方式,智能指针对象很多方面都类似于常规指针,例如ps是一个智能指针对象,则可以对他执行解除引用操作(*ps),或者是访问结构成员(ps->buffer),将他赋值给指向相同类型的常规指针,还可以将智能指针对象赋值给一个同类型的智能指针对象,但将引起一个问题

    shared_ptr<string> ps (new string("i am a cloud"));
    shared_ptr<string> vocation;
    vocation = ps;
    

    上述赋值语句完成什么工作呢,如果ps和vocation是常规指针,则两个指针指向同一个string对象,这是不能接收的,因为程序将视图删除同一个对象两次,一次是ps过期,一次是vocation过期。要避免这种问题,方法有多种。

    • 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。
    • 建立所有权(ownership):概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr 的策略,但unique_ptr.的策略更严格。
    • 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference countimg) ,例如,赋值时,计数将加1,而指针过期时,计数将减1,仅当最后一个指针过期时,才调用delete。这是shared_ptr 采用的策略。

    当然同样的策略也适用于复制构造函数。

    为什么unique_ptr强于auto_ptr
    auto_ptr<string> p1 (new string("auto"));
    auto_ptr<string> p2;
    p2 = p1;                                  // # 3
    

    在语句3中,p2接管了string对象的所有权后,p1的所有权将被剥夺,前面说过这是一件好事,可以防止p1,p2同时删除同一个对象:但如果程序随后试图使用p1,这将是一件坏事,因为p1不再指向有效的数据。

    unique_ptr<string> p1 (new string("auto"));
    unique_ptr<string> p2;
    p2 = p1;                                  // # 6
    

    编译器认为语句6非法,避免了p1不再指向有效数据的问题,所以unique_ptr更加安全。

    但是有的时候,讲一个智能指针赋值给另一个并不会留下危险的悬挂指针,假设有如下定义

    unique_ptr<string> demo(char *s){
    	unique_ptr<string> res (new string(s));
    	return res;
    }
    

    并且有如下代码

    unique_ptr<string> ps;
    ps = demo("OK");
    

    demo( )返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,返回的unique_ptr被销毁。这没有问题,因为ps拥有了string的所有权。但这里的另一个好处是返回的临时unique_ptr很快被销毁,没有机会使用它来访问无效的数据。换句诂说,没有理出禁止这种赋值。神奇的是,编译器确实允许这种赋值!

    总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique _ptr将存在一段时间,编译器将禁止这样做。

    您可能会问,unique ptr 如何能够区分安全和不安全的用法呢﹖答案是它使用了C++11新增的移动构造函数和右值引用。

    **警告:**使用new分配内存时才能使用auto_ptr和shared_ptr,使用new[]分配内存时,不能使用他们。不使用new分配内存时,不能使用auto_ptr和shared_ptr。不使用new或者new[]分配内存时,不能使用unique_ptr。

    选择智能指针

    应使用哪种智能指针呢?如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素,两个对象包含都指向第三个对象的指针,STL容器包含指针。很多STL 算法都支持复制和赋值操作,这些操作可用于shared ptr,但不能用于unique_ptr(编译器发出警告)和 auto_ ptr(行为不确定)。如果您的编译器没有提供 shared_ptr,可使用Boost库提供的shared _ptr。

  • 相关阅读:
    树上启发式合并简单题
    论文解析-基因序列编码算法DeepSEA
    Android—ATMS应用启动流程
    “先进计算”领域仍待开拓,猿代码做先进计算人才的引路人
    ③. GPT错误:python控制台运行pip list列出安装库,列出:pip install 库1 库2库3...,方便一次性安装错误
    Ubuntu 18.04.6 LTS安装NVIDIA显卡驱动&CUDA&cuDNN
    代码随想录算法训练营第四十六天 | LeetCode 139. 单词拆分、多重背包、背包总结
    【机器学习——决策树算法——Python实现——信用评级】
    4+经典思路,肿瘤+WGCNA+预后模型鉴定预后标志物
    0037【Edabit ★☆☆☆☆☆】【修改Bug 2】Buggy Code (Part 2)
  • 原文地址:https://blog.csdn.net/weixin_43903639/article/details/127069095