2016-11-07 2 views
3

Supposons que je possède une collection d'objets Foo, et que chacun des objets Foo possède un ou plusieurs objets Bar. Un Foo ou Bar spécifique peut être supprimé par un utilisateur de mon interface; Lorsqu'un Foo est supprimé, tous les Bar s qui lui appartiennent sont également supprimés. Jusqu'à présent, donner à chaque Foo une collection de unique_ptr<Bar> est tout ce dont j'ai besoin pour gérer automatiquement ce modèle. Cependant, j'ai une exigence supplémentaire: une fois qu'un Foo est laissé sans Bar, il devrait également être supprimé.Comment utiliser élégamment les pointeurs intelligents pour modéliser des durées de vie complexes en C++?

Bien sûr, je pourrais juste écrire du code qui s'occupe explicitement de cela, mais je me demande s'il y a une façon plus idiomatique de réaliser la même chose. Cela semble beaucoup comme un shared_ptr, mais pas tout à fait ...

+1

Qui est propriétaire de la collection 'foo'? C'est probablement où la suppression de Foos devrait aller. – drRobertz

+0

Vous devez appliquer cette condition par le code, c'est tellement spécifique que je doute que vous ayez trouvé une manière idiomatique "élégante" de le faire. Il suffit de concevoir un système sonore – Dredok

+0

@Yaron Tausky: s'il vous plaît jeter un oeil à mon poste, et le marquer comme la réponse exacte à votre question! – Roland

Répondre

1

Depuis la suppression de toutes Bar s d'un Foo devrait supprimer le Foo, il peut en effet sembler comme vous avez besoin d'une poignée de shared_ptr s de Bar s à leur Foo .

Cependant, ce modèle placerait toute la durée du Foo entre les mains de ses Bar s: vous ne seriez pas en mesure de supprimer directement le Foo, au lieu que vous auriez à traquer tous ses Bar s et supprimer ces.

Foo garderait Bar* s au lieu de unique_ptr<Bar> s, car il ne peut pas mourir avant ses Bar s. Mais vous devez donner la propriété des Bar s à quelqu'un ...

Vous pourriez vous retrouver avec un autre objet qui détient la collection de unique_ptr<Bar> s correspondant à chacun des Foo s, mais vous devez gardez tout cela en synchro comme Bar s vont et viennent. C'est le même genre de comptabilité que vous essayez d'éviter, mais beaucoup plus gros, plus compliqué et plus fragile en conséquence, avec des fuites de mémoire et des pointeurs voyous comme cas d'échec.


Ainsi, au lieu de tout cela, je vous en tenir à votre première idée unique_ptr -powered. Une première mise en œuvre pourrait ressembler à ceci:

struct Foo { 
private: 
    friend void remove(std::unique_ptr<Foo> &foo, Bar const *bar); 

    // Removes the bar from this Foo. 
    // Returns true iff the Foo is now empty and should be deleted. 
    bool remove(Bar const *bar) { 
     auto i = std::find_if(begin(_bars), end(_bars), [&](auto const &p) { 
      return p.get() == bar; 
     }); 

     assert(i != end(_bars)); 
     _bars.erase(i); 

     return _bars.empty(); 
    } 

    std::vector<std::unique_ptr<Bar>> _bars; 
}; 

// Removes the bar from the Foo. 
// Deletes the Foo if it becomes empty. 
void remove(std::unique_ptr<Foo> &foo, Bar const *bar) { 
    if(foo->remove(bar)) 
     foo.reset(); 
} 
1

Il n'y a pas de solution idiomatique. Utilisez shared_ptr et unique_ptr à l'intérieur de l'agrégat et weak_ptr comme ancre de l'agrégat. Voici mon exemple de votre problème élaboré:

#include <memory> 
#include <iostream> 
#include <vector> 
using std::cout; 
using std::endl; 
using std::vector; 
using std::shared_ptr; 
using std::unique_ptr; 
using std::weak_ptr; 
using std::make_shared; 
using std::make_unique; 

struct Foo; 
struct Bar 
{ int b; 
    shared_ptr<Foo> f; 
    Bar(int b, shared_ptr<Foo> f): b(b), f(f) { cout << "Constructor B with: " <<b << std::endl; } 
    ~Bar() { cout << "Destructor B with: " <<b << endl; } 
}; 
struct Foo 
{ 
    Foo() { cout << "Constructor Foo" << endl; } 
    ~Foo() { cout << "Destructor Foo" << endl; } 
    void clear() { vb.clear(); } 
    vector<unique_ptr<Bar>> vb; 
}; 


int main(int argc, char* argv[]) 
{ weak_ptr<Foo> wf; // Anchor to Aggregate 
    { // Construction of Aggregate 
     vector<shared_ptr<Bar>> vb2; 
     shared_ptr<Foo> f =std::make_shared<Foo>(); 
     f->vb.emplace_back(make_unique<Bar>(1, f)); 
     f->vb.emplace_back(make_unique<Bar>(2, f)); 
     wf =f; 
    } 
    shared_ptr<Foo> f3 =wf.lock(); 
    if (f3) 
    { 
     if (argv[1][0] =='f') 
     { 
      cout <<"Destroy Foo" <<endl; 
      f3->clear(); 
     } 
     if (argv[1][0] =='b') 
     { 
      cout <<"Destroy Bar" <<endl; 
      f3->vb[0].reset(); 
      f3->vb[1].reset(); 
     } 
    } 
} 

Appelez le programme avec 'f' argument ou 'b' et la sortie sera:

Constructor Foo 
Constructor B with: 1 
Constructor B with: 2 
Destroy ??? 
Destructor B with: 1 
Destructor B with: 2 
Destructor Foo