Pasar objeto por unique_ptr o por valor y cómo implementar

Tengo un caso en el que no estoy seguro de si debería usar unique_ptr o pasar Object por valores.

Digamos que tengo clase A que tiene un vector de clase B y clase C tiene un vector de clase B también. Cada vez que estoy agregando un Objeto B al Vector en la clase C, debe eliminarse del vector de la Clase C y viceversa. Cuando se destruye el Objeto C, todos los objetos en la clase B Vector deben agregarse al vector B en la clase A

 class B { public: B(); virtual ~B(); }; class A { C & c; std::vector bs; public: A(C & c ): c(c){}; virtual ~A(); void add(B b){ bs.push_back(b); c.remove(b); } void remove(B b){ bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end()); } }; class C { public: A & a; std::vector bs; C(A & a): a(a) { }; virtual ~C(){ for (B b : bs) { a.add(b); remove(b); } } void add(B b){ bs.push_back(b); a.remove(b); } void remove(B b){ bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end()); } }; 

Mis preguntas:

  1. ¿Es mejor usar punteros en este caso? ¡Siempre debería tener un Objeto único para B! En otras palabras, si el contenido de dos objetos b es diferente, todavía son diferentes debido a la dirección diferente en la memoria.
  2. ¡Quiero escribir este código con la ayuda de smart_pointers en C ++ 11! ¿Qué tipo es mejor shared_ptr o unique_ptr ? El objeto AB nunca es propiedad de dos objetos y siempre tiene un propietario, así que supongo que unique_ptr es mejor, pero no estoy seguro.
  3. ¿Cómo puedo escribir el código anterior utilizando unique_ptr?

  1. Si la construcción de copias B es costosa, entonces los punteros (inteligentes) son probablemente una buena idea (el rediseño de la lógica de la aplicación podría ser otra solución),

  2. Si entendí correctamente, una instancia B dada siempre es manipulada por un solo propietario (ya sea A o C ). std::unique_ptr es por lo tanto una opción razonable,

  3. Intente la siguiente implementación. No lo he comstackdo pero creo que obtendrás la idea general 🙂

.

 class B { public: B(); virtual ~B(); }; class A { C & c; std::vector> bs; public: A(C & c ): c(c){}; virtual ~A(); // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ // (c) Passing unique_ptr by value means "sink." void add(std::unique_ptr b){ c.remove(b); // release the poiner from the other container bs.emplace_back(b.get()); // emplace the pointer in the new one b.release(); // emplacement successful. release the pointer } // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ // (d) Passing unique_ptr by reference is for in/out unique_ptr parameters. void remove(std::unique_ptr& b){ // @todo check that ther returned pointer is != bs.end() std::find(bs.begin(), bs.end(), b)->release(); // first release the pointer bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end()); // then destroy its owner } }; class C { public: A & a; std::vector> bs; C(A & a): a(a) { }; virtual ~C(){ for (auto&& b : bs) { a.add(b); // a is going to call this->remove()... // unless calling this->remove() from "a" // while this is being destroyed is Undefined Behavior (tm) // I'm not sure :) } } // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ // (c) Passing unique_ptr by value means "sink." void add(std::unique_ptr b){ c.remove(b); // release the poiner from the other container bs.emplace_back(b.get()); // emplace the pointer in the new one b.release(); // emplacement successful. release the pointer } // http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/ // (d) Passing unique_ptr by reference is for in/out unique_ptr parameters. void remove(std::unique_ptr& b){ // @todo check that ther returned pointer is != bs.end() std::find(bs.begin(), bs.end(), b)->release(); // first release the pointer bs.erase(std::remove(bs.begin(), bs.end(), b), bs.end()); // then destroy its owner } }; 

Solo usaría unique_ptr si tienes que hacerlo. Es posible que prefiera hacer de B un tipo de solo movimiento (como unique_ptr ) para restringir la propiedad.

Si B es costoso de mover o no es práctico evitar la copia de B , use unique_ptr pero tenga en cuenta que está pagando por una asignación de memoria dinámica.

Aquí es cómo puede usar una B solo movimiento en un ejemplo inspirado en su código. Si usa unique_ptr en su lugar, debería funcionar exactamente igual:

 struct B { B(); B(B&&) = default; // Explicitly default the B& operator=(B&&) = default; // move functions. B(const B&) = delete; // Delete copy functions - Not strictly B& operator=(const B&) = delete; // necessary but just to be explicit. }; struct A { std::vector bs; void add(B b){ bs.push_back(std::move(b)); } B remove(std::vector::iterator itr){ B tmp = std::move(*itr); bs.erase(itr); return tmp; } }; struct C { A& a; std::vector bs; C(A& a) : a(a) {} ~C(){ for (auto& b : bs) { a.add(std::move(b)); } } // bs will be deleted now anyway, no need to remove the dead objects void add(B b){ bs.push_back(std::move(b)); } B remove(std::vector::iterator itr){ auto tmp = std::move(*itr); bs.erase(itr); return tmp; } }; int main() { A a; C c(a); a.add(B()); auto tmp = a.remove(a.bs.begin()); c.add(std::move(tmp)); } 

Demo en vivo.