c++ - 了解 shared_ptr

标签 c++ memory-management

我正在阅读 Scott Meyerses C++,现在阅读有关管理资源的部分。他解释说 shared-ptr 是一个引用计数智能指针,它的作用类似于垃圾收集器,只是它不能中断引用循环。这是什么意思?什么是引用的中断周期?

最佳答案

struct A
{
    shared_ptr<A> p ;
} ;

if ( true ) // complete extra scope for this example, just to make these things go out of scope
{
    shared_ptr<A> p1 = make_shared<A>() ;
    shared_ptr<A> p2 = make_shared<A>() ;

    p1->p = p2 ;
    p2->p = p1 ;
}
// At this point, they're out of scope and clearly won't be used again
// However, they will NOT be destroyed because they both have a strong reference to each other

这是一个循环。

垃圾收集器(了解系统)可以看到这些变量没有被任何东西引用,所以很明显它们不需要,并且会销毁它们。垃圾收集器 [通常] 可以随时运行。

但是,在 C++ 中,某些代码实际上必须执行该操作……但永远不会。

你在哪里使用它?

大型程序。

首先,我想提出几个定义:

  • 结构:保存数据(如位置或时间、位置、速度等记录)的事物 - 即您打算使用并且具有最小智能的事物给它。 (旁注:我倾向于使用“struct”来声明它们)

  • 对象:控制其数据并具有状态的事物,您可以通过对话(如调用方法或发送消息)与之交互 - 即行为类似于代理的事物(或者,你与之交谈的东西,比如数据库)。 (旁注:我倾向于使用“类”来声明它们)

一个结构以递归方式共享指向其他结构的指针几乎没有意义。

但是,假设您有一个对象:

class Database
{
public:
    // ...
    void shared_ptr<Record> recordNamed ( string const& name ) const ;
private:
    map<string,shared_ptr<Record> > mRecords ;
    //                           ^ so irritating
} ;

class Record
{
public:
    shared_ptr<Database> parentDatabase () const ;
    void reloadFromDataStore () const ; // reloads props from database
private:
    shared_ptr<Database> mpParentDatabase ;
    // maybe other data here too...
} ;

shared_ptr<Database> db = ... ; // get it from somewhere
shared_ptr<Record> record = db->recordNamed ( "christopher" ) ;

这是一个您想要循环引用的真实案例,它绝对是一个有效的案例。

在垃圾收集环境中,这非常好,因为 GC 知道谁在使用什么,并且知道这些以后不会被使用。

在 C++ 中,引用计数永远不会变为零(因为两个事件对象都指向彼此)并且没有代码可以返回并实现其他情况(如果有,它将被称为垃圾收集器!)

你会在 shared_ptr 之后阅读关于 weak_ptr 的内容,它解决了这个问题。

这是一种使用方法:

class Database
{
public:
    // ...
    void shared_ptr<Record> recordNamed ( string const& name ) const ;
private:
    map<string,shared_ptr<Record> > mRecords ;
} ;

class Record
{
public:
    shared_ptr<Database> parentDatabase () const ;
    void reloadFromDataStore () const ; // reloads props from database
private:
    weak_ptr<Database> mpParentDatabase ; // don't hold onto it strongly
    // maybe other data here too...
} ;

shared_ptr<Database> db = ... ; // get it from somewhere
shared_ptr<Record> record = db->recordNamed ( "christopher" ) ;

但是,如果你这样做会发生什么?

db.reset() ; // removes the last strong-reference to the database, so it is destroyed!
db = record->parentDatabase() ; // tries to lock the weak_ptr, but fails because it's dead! So it can only return null, or throw an exception.

如果您将 weak_ref 放在其他地方会怎么样?

class Database
{
public:
    // ...
    void shared_ptr<Record> recordNamed ( string const& name ) const ;
private:
    map<string,weak_ptr<Record> > mRecords ;
} ;

class Record
{
public:
    shared_ptr<Database> parentDatabase () const ;
    void reloadFromDataStore () const ; // reloads props from database
private:
    shared_ptr<Database> mpParentDatabase ;
    // maybe other data here too...
} ;

shared_ptr<Database> db = ... ; // get it from somewhere
shared_ptr<Record> record = db->recordNamed ( "christopher" ) ;

在这种情况下,数据库 对其记录只有弱引用...但这意味着每次需要当前不存在的内容时,它都必须从存储中创建它。这适用于基于文件的数据库,但如果数据库作为程序的一部分纯粹存在于内存中,则不行。

解决方案A

添加这样的命令:

db->close() ; // removes all references to Records and resets their references to the database

方案B

拥有一个拥有一切生命周期的“代币”:

shared_ptr<LifespanToken> token = ... ;

Database* ptr = new Database ( token ) ;
Record * record = ptr->recordNamed ( "christopher" ) ;

想法是 token 拥有生命周期。这是解决保留周期问题的手动方法,但这需要您了解人们将如何使用该系统!你必须预先知道很多。这在数据库的上下文中是有意义的,但您希望必须对最后的每一段代码都执行此操作。值得庆幸的是,循环主要出现在可以执行此操作的上下文中,并且其中大部分可以使用 weak_ptr 修复。

关于c++ - 了解 shared_ptr,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32366622/

相关文章:

c++ - linux voip库

c++ - C/C++/Objective-C 文本识别库

objective-c - 谁拥有自动释放对象?

delphi - 这里有内存泄漏吗?

c++ - 如何以及在何处为 Visual C++ OpenCV 项目添加调试符号

c++ - 在 GNU Radio QT Time Sink 中显示过去的数据

Hadoop 参数 mapreduce.map.memory.mb 和 mapreduce.map.java.opts

c++ - 检测内存泄漏的工具

c++ - 为什么抛出局部变量调用 move 构造函数?

c++ - C++在哪里调用析构函数?