Question

I'm wondering about how to (using C++11 and hopefully with backwards (boost or TR1) compatible smart pointer types) achieve:

One class instance (ModelController) owns a resource (InputConsumer), while another component (InputSender, which in this case is a singleton) has access to it.

The model is InputSender holds a list of references to InputConsumers, of which there will be many.

ModelController may have none, one, or many InputConsumers, and there may be many ModelControllers. The InputSender is NOT aware.

Here's what would be nice: A way for InputSender to track the InputConsumers assigned to it, in such a way that it can find out for itself whether the individual InputConsumers are valid or not.

It seems to me that weak_ptr is perfect for this purpose as their use requires checking this condition.

If InputSender stops tracking any of its weak_ptr refs, nothing bad happens, the corresponding InputConsumers will just experience radio silence.

If a ModelController is deleted, or if a ModelController deletes some of its InputConsumers, any InputSenders that have registered with them will recognize at the next time they try to access them that they no longer exist, and can clean up, without the need to send a message or do anything.

So the question is, is this an appropriate situation for using shared_ptr and weak_ptr? I wonder if shared_ptr is completely appropriate because the InputConsumers are conceptually owned by their ModelControllers, so they should be member variables. I dunno how much sense it makes for ModelController to only manage them via shared_ptr. I can't tell if unique_ptr works together with weak_ptr. Am I supposed to just manage the shared_ptrs in ModelController's ctor/dtor?

There may also be a well-known (not to me!) design pattern that this falls into, so if anyone knows of such a thing please tell me.

Was it helpful?

Solution

I do not have a lot of expertise in shared pointers, but yes, this seems a very appropriate use of weak_ptr.

In this case, you are just annoyed that:

  1. You would like to use InputConsumers directly as members of ModelControllers, since its a trivial ownership relation.
  2. You are forced to use a shared_ptr to make it work with a weak_ptr.

I think this is solved by using a shared_ptr as an alias of a member object. According to C++.com:

Additionally, shared_ptr objects can share ownership over a pointer while at the same time pointing to another object. This ability is known as aliasing (see constructors), and is commonly used to point to member objects while owning the object they belong to.

I never did it myself, but this seems adapted to your situation:

  • Have InputConsumers members of ModelControllers
  • Have an alias shared_ptr for each of them
  • Reference them in InputSender using weak_ptrs

EDIT

Here is a complete minimal working example:

#include <iostream>
#include <memory>

using namespace std;

// A class to try our smart pointers on
struct Foo 
{
    Foo() { cout << "constructing Foo\n"; }
    ~Foo() { cout << "destructing Foo\n"; }
};

// A class that owns some Foo as members
struct Owner
{
    // The actual members
    Foo foo1;
    Foo foo2;

    // A fake shared pointer whose purpose is:
    //   1) to be of type shared_ptr<>
    //   2) to have the same lifetime as foo1 and foo2
    shared_ptr<Owner> self;

    // A fake deleter that actually deletes nothing 
    struct Deleter
    {
        void operator() (Owner *) { cout << "pretend to delete Owner\n"; }
    };

    Owner() : self(this, Deleter()) { cout << "constructing Owner\n"; }
    ~Owner()                        { cout << "destructing Owner\n"; }
};

// A class that holds a reference to a Foo
struct Observer
{
    // A reference to a Foo, as a weak pointer
    weak_ptr<Foo> foo_ptr;

    Observer(const shared_ptr<Foo> & foo_ptr) : foo_ptr(foo_ptr)
    {
        cout << "constructing Observer\n";
    }
    ~Observer() { cout << "destructing Observer\n"; }

    void check()
    {
        if(foo_ptr.expired())
            cout << "foo expired\n";
        else
            cout << "foo still exists\n";
    }   
};  

int main()
{
    // Create Owner, and hence foo1 and foo2
    Owner * owner = new Owner;

    // Create an observer, passing an alias of &(owner->foo1) to ctor
    Observer observer(shared_ptr<Foo>(owner->self, &(owner->foo1)));

    // Try to access owner->foo1 from observer
    observer.check();
    delete owner;
    observer.check();

    return 0;
}

It prints:

constructing Foo
constructing Foo
constructing Owner
constructing Observer
foo still exists
destructing Owner
pretend to delete Owner
destructing Foo
destructing Foo
foo expired
destructing Observer

The tricky part is to be able to create a weak_ptr to owner->foo1 (would be the same for foo2). To do that, we first need a shared_ptr that is an alias of owner->foo1. This can only be done by:

shared_ptr<Foo> alias(other_shared_ptr, &(owner->foo1));

where other_shared_ptr is a shared_ptr<T> whose lifetime is at least as long as the one of owner->foo1. To achieve this, using a shared_ptr<T> which is also a member of owner is a good idea, since it ensures the lifetime would be the same. Finally, we need a valid non-null pointer to give to it, and since we don't want to create anything on the heap, we must use an existing object. this is a good candidate, since we know it is valid and get destroyed only after its members are destroyed. Hence our other_shared_ptr is for instance:

shared_ptr<Owner> self(this);

However, this means that when self gets out of scope, i.e. during the destruction of owner, it will call delete this. We do not want this deletion to occur, otherwise this would be deleted twice (which is undefined behaviour, in practice a segfault). Hence we also provide to the constructor of self a Deleter that actually doesn't delete anything.

The rest of the code should be self-explanatory with the comments.

OTHER TIPS

Just one more complete working snippet, showing std::weak_ptr dynamics, maybe can help a little bit more. (I particulary liked this expired() semantics);

#include<iostream>
#include<memory>
#include<string>

class MessageProcessor{                
};

class Message{
public:

        Message(std::shared_ptr<MessageProcessor>  _msg_proc, int _id){
                proc_weak = std::weak_ptr<MessageProcessor>(_msg_proc);
                proc_shared = _msg_proc;
                id = _id;
        }

        std::weak_ptr<MessageProcessor> proc_weak;
        std::shared_ptr<MessageProcessor> proc_shared;        
        int id;
};

int main(){

        std::shared_ptr<MessageProcessor> proc(new MessageProcessor());

        Message msg(proc,1);

        // Here we have proc with 2 shared_ptr refs: 'proc' and 'msg.proc_shared'
        // As expected 'msg.proc_weak is not expired'
        if( !msg.proc_weak.expired() )
                std::cout << "1) proc_weak is not EXPIRED. proc.use_count() == " << proc.use_count() << std::endl;        

        // make one of shared_ptr ref, point to other place
        msg.proc_shared = std::shared_ptr<MessageProcessor>();        

        // there is still the 'proc' reference        
        if( !msg.proc_weak.expired() )
                std::cout << "2) proc_weak is not EXPIRED (yet). proc.use_count() == " << proc.use_count() << std::endl;

        // 'erase' the last reference
        proc = std::shared_ptr<MessageProcessor>(); 

        // Finally... There is no more refs in shared_pointer!
        if( msg.proc_weak.expired() )
                std::cout << "3) proc_weak has EXPIRED. proc.use_count() == " << proc.use_count() << std::endl;

        return 0; 
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top