Argonne National Laboratory

Experimental Physics and
Industrial Control System

2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  <20182019  2020  2021  Index 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  <20182019  2020  2021 
<== Date ==> <== Thread ==>

Subject: wrapped shared_ptr games
From: Michael Davidsaver <mdavidsaver@gmail.com>
To: Marty Kraimer <mrkraimer@comcast.net>
Cc: EPICS core-talk <core-talk@aps.anl.gov>
Date: Mon, 18 Jun 2018 11:46:48 -0700
Marty,

As follow up to our discussion (and head scratching) last week.
Attached is an annotated example of the using wrapped/nested
shared_ptr to maintain a list of shared_ptr with automatic
cleanup.

Michael



As a note.  This is _a_ way to keep the equivalent
of "list<weak_ptr<C> >".  There are at least two other
ways to do this.  I'm not going to say which is better
or worse.


1. "list<C*>"  keep raw pointers and call "list.remove(this)"
   from "C::~C".

2. Keep "list<weak_ptr<C> >" and occasionally (eg. when adding)
   loop through and erase() any which have "weak_ptr::expired()".
/* Example of using wrapped/nested shared_ptr
 * to maintain a list of "weak" references without
 * actually using weak_ptr.
 *
 * The plain, unwrapped, shared_ptr<Channel> are called "internal" and
 * will call the delete operator, and therefore the Channel destructor,
 * when the "internal" ref count falls to zero.
 * 
 * This is desirable in cases where std::enabled_share_from_this<Channel>
 * would be preferred, but can not be as shared_from_this()
 * can't be used in Channel destructor.
 *
 * The wrapped/nested shared_ptr<Channel> are called "external", and
 * will call Channel::cleanup() when the "external" ref count calls to
 * zero.  The destroyer object associated with the "external" ref counter
 * contains an "internal" reference.  This ensures that the Channel
 * object is not free'd until after cleanup() returns, and allows
 * cleanup to (temporarily) create new "internal" references.
 *
 * g++ -O2 -o sharedmap -std=c++11 -g -Wall -Werror sharedlist.cpp && valgrind ./sharedlist
 */
#include <iostream>
#include <string>
#include <list>
#include <memory>
#include <cassert>

namespace {

struct Channel;
struct Context;

// our example Context is only a list of Channel references.
struct Context {
    // This list contains only "internal" Channel references.
    std::list<std::shared_ptr<Channel> > channels;
    
    ~Context() {
        std::cout<<__func__<<'\n';
        // Channels should be removed before they are destoryed.
        // and Context shouldn't be destroyed until all Channels are destroyed.
        assert(channels.empty());
    }

    void show_channels();
};

struct Channel {
    const std::string name; // for display purposes only
    const std::shared_ptr<Context> context;

    // Can't use std::enable_shared_from_this<Channel> as it's magic
    // interactions with the shared_ptr constructor conflict with
    // the wrapping done in our build() below.
    //
    // This weak pointer is to our internal (unwrapped) reference.
    std::weak_ptr<Channel> internal_self;
    // we could also store a weak ref to our external (wrapped) ref.
    // but this example doesn't need it.

    Channel(const std::string& name,
            const std::shared_ptr<Context>& context
    ) :name(name), context(context) {}

    ~Channel() {
        std::cout<<__func__<<" "<<name<<'\n';
    }

    // cleanup() functions like a destructor, but only when all external
    // references are released.
    void cleanup() {
        std::shared_ptr<Channel> SELF(internal_self);
        std::cout<<__func__<<" "<<SELF->name<<'\n';
        context->channels.remove(SELF);
    }

    // cleaner functor object.
    // This object is stored along side the "external" ref counter.
    // Thus it is not destoryed until all strong and _weak_ "external" refs are released.
    struct cleaner {
        std::shared_ptr<Channel> internal;
        explicit cleaner(const std::shared_ptr<Channel>& internal) :internal(internal) {}
        void operator()(Channel *ignore) {
            std::shared_ptr<Channel> temp;
            // consume stored reference.
            // not strict needed in this example, as no weak "external" refs exist.
            // but better to be in the habit then to be surprised by a "weak ref loop"
            // which leaks the "internal" ref!
            temp.swap(internal);
            temp->cleanup();
            // temp goes out of scope, which _may_ run Channel dtor provided no internal strong refs remain.
            // Practical multi-threaded code could used cleanup() to wait for such refs to be released,
            // and perhaps to join any worker threads which have them.
            assert(temp.use_count()==1);
        }
    };
    /* with c++11 "cleaner(internal)" can be replaced with
     * 
     * [internal] (Channel* ignore) {
     *     std::shared_ptr<Channel> temp(std::move(internal));
     *     temp->cleanup();
     * }
     */

    static std::shared_ptr<Channel> build(const std::string& name,
                                            const std::shared_ptr<Context>& context)
    {
        std::cout<<__func__<<" "<<name<<'\n';
        std::shared_ptr<Channel> internal(new Channel(name, context)),
                                 external(internal.get(), cleaner(internal));
        external->internal_self = internal;
        // could save weak_ptr to external here, but no reason in this example
        context->channels.push_back(internal);
        assert(external.use_count()==1);
        return external;
    }
};

void Context::show_channels() {
    std::cout<<"Channels:\n";
    for(auto& chan : channels) {
        std::cout<<" "<<chan->name<<'\n';
    }
}

} // namespace

int main(int argc, char *argv[])
{
    try {
        std::shared_ptr<Context> ctxt(new Context);

        std::cout<<"Build channel \"bar\"\n";

        std::shared_ptr<Channel> bar(Channel::build("bar", ctxt));

        {
            std::cout<<"Build channel \"foo\"\n";

            std::shared_ptr<Channel> foo(Channel::build("foo", ctxt));
            ctxt->show_channels(); // shows "foo" and "bar"

            std::cout<<"channel foo external ref goes out of scope\n";
        }

        ctxt->show_channels(); // shows only "bar"
        
        std::cout<<"channel bar external ref goes out of scope\n";

        return 0;
    }catch(std::exception& e){
        std::cerr<<"Error: "<<e.what()<<'\n';
        return 2;
    }
}

Navigate by Date:
Prev: Re: Problem with INSTALL_LOCATION and "make uninstall" in 7.0.1 J. Lewis Muir
Next: Re: Problem with INSTALL_LOCATION and "make uninstall" in 7.0.1 Michael Davidsaver
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  <20182019  2020  2021 
Navigate by Thread:
Prev: Re: Q: Structured argument types for PVA RPC service call Ralph Lange
Next: Build failed in Jenkins: epics-7.0-windows » STATIC,win64 #45 APS Jenkins
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  <20182019  2020  2021 
ANJ, 21 Jun 2018 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·