stella
stella

Reputation: 2596

Understding shared_ptr

I'm reading Scott Meyerses C++ and now at the section about managing resources. He explains that the shared-ptr is a reference counting smart pointer and that it acts like a garbage collector except that it can't break cycles of reference. What does it mean? What is the breaking cycles of references?

Upvotes: 2

Views: 320

Answers (1)

iAdjunct
iAdjunct

Reputation: 2979

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

This is a cycle.

A garbage collector (which has knowledge of the system) can see that those variables aren't referenced by anything, so clearly they're not needed, and will destroy them. A garbage collector can [generally] run whenever it wants to.

However, in C++, some code actually has to perform that action... but nothing ever will.

Where do you use this?

Large programs.

Firstly, I'd like to propose a couple definitions:

  • Structure: a thing that holds data (like a position or a record of time, position, velocity, etc) - i.e a thing you're meant to use and has minimal smarts to it. (Sidenote: I tend to declare these using 'struct')

  • Object: a thing which controls its data and has a state which you interact with by a conversation (like calling methods or sending messages) - i.e. a thing which acts like an agent (or, a thing that you talk to, like a database). (Sidenote: I tend to declare these using 'class')

It rarely makes sense for a structure to have shared pointers to other structures in a recursive fashion.

However, let's say you have an object:

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" ) ;

This is a real-world case where you'd want to have circular references, and it is absolutely a valid one.

In a garbage-collected environment, this is perfectly fine because the GC knows who's using what and will know these aren't being used later.

In C++, the reference-count never goes to zero (because two living objects both point to each other) and there is no code to come back through and realize otherwise (if there were, it would be called a garbage collector!).

You'll read after shared_ptr about weak_ptr, which solves this issue.

Here's one way to use it:

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" ) ;

However, what happens if you do this?

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.

What if instead you put the weak_ref somewhere else?

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" ) ;

In this case, the Database only has weak references to its records... but that means it has to go create it from a store every time it needs something that isn't currently alive. This would work with file-based database, but not if the database is something that lives purely in memory as part of your program.

Solution A

Add a command like this:

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

Solution B

Have a "token" that owns the lifespan of everything:

shared_ptr<LifespanToken> token = ... ;

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

The idea being that the token owns the lifespan. This is the manual way to get around the retain-cycle issue, but this requires you to know how people will use the system! You have to know a LOT up front. This can make sense in the context of a database, but you would not want to have to do this for every last piece of your code. Thankfully, cycles come up mostly in contexts that CAN do this, and most of them can be fixed with weak_ptr.

Upvotes: 7

Related Questions