智能指针的应用

Posted 从前,有个傻子........

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智能指针的应用相关的知识,希望对你有一定的参考价值。

智能指针解决的问题:一种是忘记释放内存形成泄露,另一种是尚有指针引用时就释放了它,产生引用非法内存的指针。
指针的作用:
1. 更好的管理内存
2. 实质是一个对象,行为表现像一个指针
3. 防止忘记用delete释放内存和程序异常的进入catch块忘记释放内存
4. 将普通的指针封装成一个栈对象,当栈对象的生存周期结束后,会在析构函数中释放申请的内存,防止泄露
原理:智能指针是一个类,超出类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放内存。
Auto_ptr:原型

void testPointer::RunPtr2() 
    std::auto_ptr<TObj> obj(new TObj);//obj指向一个TObj
    obj->testData();
    std::auto_ptr<TObj> obj1 = obj;//转移指针
    obj1->testData();
    obj->testData(); //程序崩溃

复制auto_ptr对象时,把指针指传给复制出来的对象,原有对象的指针成员随后重置为nullptr。这说明auto_ptr是独占性的,不允许多个auto_ptr指向同一个资源。

Shared_ptr:资源可被多个指针共享,使用计数机制表明资源被几个指针共享,引用计数的内存在堆上分配,智能指针是个模板类,指定类型,传入指针通过构造函数初始化。**注意:不能将指针直接赋值给一个智能指针,智能指针是类,不要用一个原始指针初始化多个shared_ptr,会造成二次释放同一个内存,注意循环引用问题。 **
多个shared_ptr指向同一处资源,当所有shared_ptr都全部释放时,该处资源才释放。
(有某个对象的所有权(访问权,生命控制权) 即是强引用,所以shared_ptr是一种强引用型指针)

内部实现:

void testPointer::testSharedPointer() 
    std::shared_ptr<TObj> tobj(new TObj());//计数+1
    std::shared_ptr<TObj> tobj1 = tobj;//计数+2
    std::shared_ptr<TObj> tobj2 = tobj;//计数+3
    //栈退出后,shared_ptr释放 计数减为0 指向的堆被释放

void testPointer::testSharedPointer1() 
    std::shared_ptr<TObject> parent(new TObject());
    std::shared_ptr<TObject> child(new TObject());
    parent->setChild(child);
    child->setParent(parent);//栈退出时不执行析构函数

函数退出时栈的shared_ptr对象释放后的情形:
(1)一开始:father,son指向的堆对象 shared计数都是为2;
(2)son智能指针退出栈:son指向的堆对象 计数减为1,father指向的堆对象 计数仍为2。
(3)father智能指针退出栈:father指向的堆对象 计数减为1 , son指向的堆对象 计数仍为1。
(4)函数结束:所有计数都没有变0,也就是说中途没有释放任何堆对象。
为了解决这一缺陷的存在,弱引用指针weak_ptr的出现很有必要。
**shared_ptr自动销毁所管理的对象:**当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过另一个特殊的成员函数-析构函数完成销毁工作的。析构函数一般用来释放对象所分配的资源。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。
**shared_ptr还会自动释放相关联的内存:**当动态对象不再被使用时,shared_ptr类还会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

尽量不要混合使用普通指针和智能指针
如果混合使用的话,智能指针自动释放之后,普通指针有时就会变成悬空指针,当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。也不要使用get初始化另一个智能指针或为智能指针赋值。

智能指针和异常:p是一个shared_ptr,因此p销毁时会检测引用计数,当发生异常时,我们直接管理的内存是不会自动释放的。如果使用内置指针管理内存,且在new之后在对应的delete之前发生了异常,则内存不会被释放。

Unique_ptr:同一时刻只能有一个unique_ptr指向指定对象,独占是最大特性
生命周期:指针创建开始直到离开作用域,离开时若其指向对象,将其所指向的对象销毁
使用std:move可将一个unique_ptr赋值给另一个
Boost库boost:scoped_ptr是独占性智能指针,不允许转移所有权

void testPointer::testUniquePtr() 
    std::unique_ptr<TObj> tobj(new TObj());
    //std::unique_ptr<TObj> tobj1 = tobj;//error
    tobj->testData();//

不能拷贝或者赋值unique_ptr,但是可以通过调用release或reset将指针所有权从一个(非const)unique_ptr转移给另一个unique。

void testPointer::testUniquePtr1() 
    std::unique_ptr<TObj> tobj(new TObj());
    std::unique_ptr<TObj> tobj1(tobj.release());
    tobj1->testData();

Weak_ptr:shared_ptr存在内存泄漏的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会形成循环引用,使引用计数失败,导致内存泄漏。
Week_ptr协助shared_ptr工作,以旁观者观测资源使用情况,weak_ptr可以从一个shared_ptr或另一个weak_ptr对象构造获取观测权,没有共享资源,它的构造和析构不会引起引用计数的增加或减少。weak_ptr是为了辅助shared_ptr的存在,它只提供了对管理对象的一个访问手段,同时也可以实时动态地知道指向的对象是否存活。
(只有某个对象的访问权,而没有它的生命控制权 即是 弱引用,所以weak_ptr是一种弱引用型指针)
内部大概实现:
1.计数区域(SharedPtrControlBlock)结构体引进新的int变量weak_count,来作为弱引用计数。
2.每个weak_ptr都占指针的两倍空间,一个装着原始指针,一个装着计数区域的指针(和shared_ptr一样的成员)。
3.weak_ptr可以由一个shared_ptr或者另一个weak_ptr构造。
4.weak_ptr的构造和析构不会引起shared_count的增加或减少,只会引起weak_count的增加或减少。
被管理资源的释放只取决于shared计数,当shared计数为0,才会释放被管理资源,也就是说weak_ptr不控制资源的生命周期。但是计数区域的释放却取决于shared计数和weak计数,当两者均为0时,才会释放计数区域。

空悬指针问题是指:无法知道指针指向的堆内存是否已经释放。得益于引入的weak_count,weak_ptr指针可以使计数区域的生命周期受weak_ptr控制,从而能使weak_ptr获取 被管理资源的shared计数,从而判断被管理对象是否已被释放。(可以实时动态地知道指向的对象是否被释放,从而有效解决空悬指针问题)它的成员函数expired()就是判断指向的对象是否存活。

class TObject  : public QObject

	Q_OBJECT
public:
	TObject(QObject* parent = 0);
	~TObject();
	void setParent(std::shared_ptr<TObject> &parent);
	void setChild(std::shared_ptr<TObject> &child);
private:
	std::weak_ptr<TObject> m_parent;
	std::weak_ptr<TObject> m_child;
;
void testPointer::testSharedPointer1() 
    std::shared_ptr<TObject> parent(new TObject());
    std::shared_ptr<TObject> child(new TObject());
    parent->setChild(child);
    child->setParent(parent);//退出栈时析构函数被调用

可以说,当生命控制权没有彼此互相掌握时,才能正确解决循环引用问题,而弱引用的使用可以使生命控制权互相掌握的情况消失。
weak_ptr没有重载 * 和 -> ,所以并不能直接使用资源。但可以使用lock()获得一个可用的shared_ptr对象,如果对象已经死了,lock()会失败,返回一个空的shared_ptr。

void testPointer::testUniquePtr1() 
    std::shared_ptr<TObj> tobj(new TObj());
    std::weak_ptr<TObj> tobj1;
    if (auto observe = tobj1.lock()) 
        qDebug() << "0-----lock--------------------";
    
    else 
        qDebug() << "0-----not lock--------------------";//执行
    
    
		tobj1 = tobj;
		if (auto observe = tobj1.lock()) 
			qDebug() << "1--------lock--------------------";//执行
		
		else 
			qDebug() << "1--------not lock--------------------";
		
    

在具有自动存储持续时间的对象上使用智能指针

【中文标题】在具有自动存储持续时间的对象上使用智能指针【英文标题】:Using of Smart Pointers on objects with automatic storage duration 【发布时间】:2016-09-13 07:33:42 【问题描述】:

如果我将智能指针(scoped_ptrunique_ptrshared_ptr)应用于具有自动存储持续时间(即存储在堆栈上)的对象,是否会出现问题?我假设不是在这两种情况下(有和没有智能指针),如果没有更多指针指向它们,它们将被删除。指针本身始终具有自动存储持续时间,即如果超出范围,它们将被删除。

【问题讨论】:

只要您还包含一个不删除对象的自定义删除器,您就可以... 【参考方案1】:

是的,有一个问题(一个大问题):您正在使用智能指针来做某事(我不知道是什么),而它们并不是设计用来做的。它们旨在管理动态分配对象的生命周期。它们解决了从C 继承的问题,它们为需要动态分配/释放的对象提供所有权(它们不限于内存(指针);您实际上可以使用智能指针来管理需要获取/释放的任何类型的资源,例如系统资源、文件描述符等)。

好的,但是除了哲学问题,还有实际问题吗?

是的!!。智能指针会在拥有的对象上调用delete/delete[],当指针不是分别从new/new[]获取时,即为Undefined Behavior。

是的,您可以使用一个什么都不做的自定义删除器,但是使用智能指针有什么意义呢?

【讨论】:

将 smart_ptr 与无操作删除器一起使用有什么意义?假设您有一个map&lt;shared_ptr&lt;X&gt;, Y&gt;,由int X::id_ 索引,因为大多数类逻辑都规定(甚至可能在您无法更改的库中)。现在,您要查找与某个 int id 值对应的 Y。您可以做的是在堆栈上创建一个虚拟X,并将密钥设置为您的id(以及默认设置的其余部分)使用无操作删除器,执行搜索并开心(而不是迭代地图 - 甚至可能无法访问 - 或从堆中分配 X【参考方案2】:

这里是demo:

class Foo
    public:
        Foo()cout << "Foo created!\n";
        ~Foo()cout << "Foo destroyed!\n";
;
void go()
    Foo foo;
    cout << "Position A\n";
    
        Foo foo1;
    
    cout << "Position B\n";
    
        Foo *foo2;
    
    cout << "Position C\n";
    
        Foo *foo3 = new Foo;
    
    cout << "Position D\n";
    
        std::shared_ptr<Foo> foo3(new Foo);
    
    cout << "Position E\n";
    
        std::shared_ptr<Foo> foo3(&foo);
    
    cout << "Position F\n";

输出如下:

Foo created!
Position A
Foo created!
Foo destroyed!
Position B
Position C
Foo created!
Position D
Foo created!
Foo destroyed!
Position E
Foo destroyed!

结尾

运行时错误: 错误:free():无效指针:0x00007ffda60f0e67

注意位置 D-E 和位置 E-F 之间的区别(删除不是来自 new 的指针,因此会导致 未定义的行为

【讨论】:

请与我们分享您在哪里使用自定义的无操作删除器。【参考方案3】:

是的,这是一个问题,因为智能指针将(暂时忽略自定义删除器)在不是通过 new(即 UB)创建的对象上调用 delete,并且析构函数将被调用两次(这会导致也有未定义的行为)。

顺便说一句。如果您尝试通过两个独立的智能指针管理对象,也会导致错误。

因此,就生命周期管理而言,“越多越好” 绝对不是真的。

【讨论】:

以上是关于智能指针的应用的主要内容,如果未能解决你的问题,请参考以下文章

Qt 智能指针学习

Qt智能指针

Qt6STL-QVector循环删除指针浅谈

Qt 半内存管理

C++智能指针类模板

Qt delete & deletelater设计