mardi 2 février 2016

C++ safe idiom to call a member function of a class through a shared_ptr class member

In designing an observer pattern for my code, I encountered the following task: I have a class Foo which contains a variable std::shared_ptr<Bar> and I want to use a weak_ptr<Bar> to this shared-pointer to safely call a function update() in Foo.

Here is an example code:

struct Bar
{
    void call_update_in_foo() { /* how to implement this function? */}
};

struct Foo
{
    virtual void update() = 0;
    std::shared_ptr<Bar> bar;
};

As mentioned there is a weak_ptr<Bar> from which I want to call Foo::update() -- at most once -- via Bar::call_update_in_foo():

Foo foo;
std::weak_ptr<Bar> w (foo.bar);

auto s = w.lock();
if(s)
{
    s->call_update_in_foo(); //this shall call at most once Foo::update()
                             //regardless how many copies of foo there are.
}

(fyi: the call of update() should happen at most once because it updates a shared_ptr in some derived class. However imo it doesn't affect the question about "safeness".)

  • What is an appropriate implementation of Foo and Bar to carry out that process in a safe manner?


Here is an attempt for a minimal implementation -- the idea is that Bar manages a set of currently valid Foo objects, of which one member is called:

struct Bar
{
    std::set<Foo *> foos;
    void call_update_in_foo() const
    {
        for(auto& f : foos)
        {
            f->update();
            break;  //one call is sufficient
        }
    }
};

The class Foo has to take care that the std::shared_ptr<Bar> object is up-to-date:

struct Foo
{
    Foo()
    {
        bar->foos.insert(this);
    }

    Foo(Foo const& other) : bar(other.bar)
    {
        bar->foos.insert(this);
    }

    Foo& operator=(Foo rhs)
    {
        std::swap(*this, rhs);
        return *this;
    }

    ~Foo()
    {
        bar->foos.erase(this);
    }

    virtual void update() = 0;

    std::shared_ptr<Bar> bar = std::make_shared<Bar>();
};

DEMO

  • Is this already safe? -- "safe" meaning that no expired Foo object is called. Or are there some pitfalls which have to be considered?

  • If this code is safe, how would one implement the move constructor and assignment?

(I know this has the feeling of being appropriate for CodeReview, but it's rather about a reasonable pattern for this task than about my code, so I posted it here ... and further the move constructors are still missing.)

Aucun commentaire:

Enregistrer un commentaire