Tupel mit shared-ownership "zerlegen"?



  • Hey Leute,

    Ich habe gerade wieder einen "jetzt hätte ich gerne einen GC"-Moment

    ich habe ein Tupel, zur Vereinfachung ein Paar, welches mit einem boost::shared_ptr verwaltet wird. Nun muss ich das erste Element des Paars an ein anderes Objekt weiter geben. Wieder weiß ich nicht, wie lange das Element in diesem Objekt benötigt wird und will wieder shared ownership verwenden, also in etwa sowas:

    boost::shared_ptr<std::pair<T1,T2> > myPair;
    //das funktioniert selbstverständlich nicht, sondern führt zur doppelten Löschung.
    boost::shared_ptr<T1> mySubElement(&myPair->first);
    

    Eine saubere Lösung wäre:

    std::pair<boost::shared_ptr<T1>,boost::shared_ptr<T2> > myPair;
    boost::shared_ptr<T1> mySubElement(myPair.first);
    

    Hat aber den Nachteil, dass ich mehrfache Referenzzählung habe, und ich die Indirektion verschiebe. Damit mache ich aus einem Implementationsdetail, der Referenzzählung, eine Sache, mit der jeder Nutzer in Kontakt kommt. Und das will ich nicht. Was ich gerne machen würde, ist dem shared_ptr in mySubElement sagen, dass er die selbe Referenzzählung verwenden soll, wie der von myPair und dann einen Destruktor hinzufügen, der automatisch den richtigen Speicherbereich löscht. Ist das irgendwie möglich? Oder muss ich mir da was zusammen hacken?



  • Mehrfache Referenzzählung hat das auch, aber das lässt sich nicht vermeiden, wenn es wirklich ein weiterer shared_ptr werden soll.

    boost::shared_ptr<std::pair<T1,T2> > myPair;
    
    boost::shared_ptr<T1> mySubElement(
    	&myPair->first,
    	[myPair](T1 *) //custom deleter, der das Objekt am Leben hält
    {
    });
    

    EDIT: Tatsächlich bietet shared_ptr einen passenden Konstruktor an, was hustbaer gezeigt hat.



  • Du kannst dir einen eigenen Deleter zusammenhacken:

    namespace pair_owning_ {
      template <typename Owner>
      struct shared_deleter {
        Owner owner;
        shared_deleter(Owner const& owner) : owner(owner) {}
        template <typename T> void operator() (T*) { owner.reset(); }
      };
      template <typename Owner>
      shared_deleter<Owner> make_shared_deleter(Owner const& owner)
      { return shared_deleter<Owner>(owner); }
    }
    
    template <typename P>
    boost::shared_ptr<typename P::first_type>
    own_first(boost::shared_ptr<P> const& p)
    {
      typedef boost::shared_ptr<typename P::first_type> Ret;
      return Ret(&p->first, pair_owning_::make_shared_deleter(p));
    }
    
    template <typename P>
    boost::shared_ptr<typename P::second_type>
    own_second(boost::shared_ptr<P> const& p)
    {
      typedef boost::shared_ptr<typename P::second_type> Ret;
      return Ret(&p->second, pair_owning_::make_shared_deleter(p));
    }
    

    Wenn ich die Funktionsweise von shared_ptrs verstanden habe, dürfte das die Anwendung absolut sicher sein, wenn nur die Funktionen own_first und own_second aufgerufen werden (daher der namespace um die Helfer).

    Beispielanwendung:

    template <typename A, typename B>
    struct pair {
      A first;
      B second;
      typedef A first_type;
      typedef B second_type;
      pair(A first, B second)
        : first(first), second(second)
      { std::cout << "construct pair"; }
      ~pair() { std::cout << "destruct pair"; }
    };
    
    int main()
    {
      std::cout << "create pair:  {";
      boost::shared_ptr<pair<char, int> > p(new pair<char, int>('x', 5));
      std::cout << "}\n";
    
      std::cout << "own first:    {";
      boost::shared_ptr<char> f = own_first(p);
      std::cout << "}\n";
    
      std::cout << "own second:   {";
      boost::shared_ptr<int> s = own_second(p);
      std::cout << "}\n";
    
      std::cout << "unown pair:   {";
      p.reset();
      std::cerr << "}\n";
    
      std::cout << "unown first:  {";
      f.reset(new char('b'));
      std::cerr << "}\n";
    
      std::cout << "unown second: {";
      s.reset(new int(3));
      std::cerr << "}\n";
    }
    

    Ausgabe:

    create pair:  {construct pair}
    own first:    {}
    own second:   {}
    unown pair:   {}
    unown first:  {}
    unown second: {destruct pair}
    


  • Typisch Akademiker. Baut mega komplizierte Lösungen um sich vom FIAE abzusetzen.



  • Dann zeig mal deine einfachere Lösung.



  • inf. schrieb:

    Typisch Akademiker. Baut mega komplizierte Lösungen um sich vom FIAE abzusetzen.

    Meinst du mich? Ich bin noch Schüler in einem Gymnasium.



  • @otze
    Ich sehe hier nur zwei brauchbare Lösungen.

    1. tuple<shared_ptr> statt shared_ptr<tuple>, mit all den von dir erwähnten Nachteilen

    2. einen "inner shared_ptr" erzeugen. Der hält dann das gesamte Tuple am Leben, zeigt aber auf ein Element des Tuples. Beispiel:

    #include <utility>
    #include <iostream>
    #include <memory>
    
    struct other
    {
        other(std::string const& foo) : m_foo(foo) {}
        ~other() { std::cout << "~other(" << m_foo << ")\n"; }
        std::string m_foo;
    };
    
    int main()
    {
        std::shared_ptr<int> spi;
    
        {
            std::shared_ptr<std::pair<int, other> > spp = std::make_shared<std::pair<int, other> >(42, other("arg"));
            spp->second.m_foo = "shared_ptr copy";
            spi = std::shared_ptr<int>(spp, &(spp->first)); // selber ref-count und deleter wie "spp", zeigt auf "&(spp->first)"
        }
    
        std::cout << *spi << "\n";
        std::cout << "resetting spi...\n";
        spi.reset();
        std::cout << "done.\n";
    }
    

    http://ideone.com/wpupq

    @seldon
    Hier, bitte: die einfachere Lösung.



  • Ui, sehr cool. Ich wusste gar nicht, dass shared_ptr sowas kann, dabei stehts ja hier. 🙂
    http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr

    template< class Y >
    shared_ptr( const shared_ptr<Y>& r, T *ptr );
    

    Constructs a shared_ptr which shares ownership information with r, but holds an unrelated and unmanaged pointer ptr. Even if this shared_ptr is the last of the group to go out of scope, it will call the destructor for the object originally managed by r. However, calling get() on this will always return a copy of ptr. It is the responsibility of the programmer to make sure that this ptr remains valid as long as this shared_ptr exists, such as in the typical use cases where ptr is a member of the object managed by r or is an alias (e.g., downcast) of r.get()



  • Wow. Ich habe zwar die Doku gewälzt, aber da sirgendwie +berlesen. Da sist genau das, was ich gesucht habe!

    Danke!



  • Sehr gut.
    Ich war mir jetzt nicht sicher ob du das wolltest, oder eine Lösung in der pair.first am Leben erhalten wird, pair.second allerdings gleich zerstört. Zweiteres geht nämlich nicht wirklich 😉



  • hmm. ja, 100% glücklich bin ich in der Tat nicht, jetzt wo ich es mal implementiert habe.

    Im Grunde ist meine Anforderung wie folgt:

    Ich habe zwei Mengen von Objekten

    std::vector<boost::shared_ptr<A> > myAs;
    std::vector<boost::shared_ptr<B> > myBs;

    und ich muss sie in irgendeiner weise als
    std::vector<boost::shared_ptr<MyPair<A,B> > > myABPairs;

    ausdrücken können. Diese Abhängigkeit muss in 2 Richtungen erfolgen. Ich muss sowohl aus myABPairs wieder myAs und myBs erzeugen können, als auch aus MyAs und myBs das myABPair. Wir haben gerade hier die eine Richtung gelöst. Die andere Richtung bleibt ein Mysterium :).

    Die nächste Anforderung ist, dass ich irgendwie die Existenz der shared_ptr verstecken will, da die Implementation der shared-ownership ein Implementationsdetail ist, das wir eventuell auch noch einmal ändern wollen.

    Von außen sieht der User am Ende nur:

    Container<A> myAs;
    Container<B> myBs;
    //MyPair ist von mir als Entwickler frei wählbar :)
    Container<MyPair<A,B> > myABPairs = zip(myAs,myBs);
    Container<A> myAs2 = splitFirst(myABPairs);
    Container<B> myBs2 = splitSecond(myABPairs);
    

    Ich hatte eine Lösung implementiert, wobei
    Container<MyPair<T1,T2> > spezialisiert war und intern einfach nur Container<A> und Container<B> speicherte. Es wurden dann bei jedem Elementzugriff Proxyobjekte für MyPair<A,B> erzeugt. Das war ein riesiger Aufwand (fast 1000 Zeilen template Alptraum) und führte schließlich zu dem Problem, dass das Resultat kein Container im Sinne des Standards ist(im Gegensatz zu Container<A> und Container<B>). Wenn ich das Umgehe, indem ich einfach ein anderes Objekt benutze (MyPairContainer<A,B>) habe ich aber noch viel viel mehr Code-Duplikation, weil eine Menge Anwendungscode auf Container<T> templatisiert ist.

    Eine Lösung die mir momentan vorschwebt ist: ich schreibe MyPair um, so dass es folgendes interface hat:

    template<class A, class B>
    struct MyPair{
    private:
        shared_ptr<A> aRef;
        shared_ptr<B> bRef;
    public:
        A& first(){
            return *aRef;
        }
        B& second(){
            return *aRef;
        }
    };
    

    und dann spezialisiere ich meinen Container darauf. Wenn ich das geschickt anstelle, kann ich 80-90% in irgendeine Basis oder abgeleitete Klasse auslagern und muss nicht ewig viel duplizieren. Naja und halt jede Anwendung von first durch first() ersetzen.

    Bessere Vorschläge? Das ist sogar für meine Verhältnisse ein ziemlicher Hack.


Anmelden zum Antworten