¿Std :: vector está copiando los objetos con un push_back?

169

Después de muchas investigaciones con valgrind, he llegado a la conclusión de que std :: vector hace una copia de un objeto que desea hacer retroceder.

¿Es eso realmente cierto? ¿Un vector no puede mantener una referencia o un puntero de un objeto sin una copia?

Gracias

benlaug
fuente
20
Este es un principio básico de C ++. Los objetos son valores. La asignación hace una copia. Dos variables que se refieren al mismo objeto no son posibles a menos que modifique el tipo con *o &para hacer un puntero o referencia.
Daniel Earwicker
8
@DanielEarwicker push_back realmente toma una referencia. No está claro solo por la firma que hará una copia.
Brian Gordon
3
@BrianGordon - ¡No digo que lo sea! De ahí la necesidad del principio rector. Aun así, podemos deducir algo de la firma de push_back: se necesita a const&. O arroja el valor (inútil), o hay un método de recuperación. Por lo tanto, observamos la firma de backy devuelve simple &, por lo que se copió el valor original o constse ha descartado silenciosamente (muy mal: comportamiento potencialmente indefinido). Asumiendo que los diseñadores de vectoreran racionales ( vector<bool>no obstante) concluimos que hace copias.
Daniel Earwicker

Respuestas:

183

Sí, std::vector<T>::push_back()crea una copia del argumento y la almacena en el vector. Si desea almacenar punteros a objetos en su vector, cree un en std::vector<whatever*>lugar de std::vector<whatever>.

Sin embargo, debe asegurarse de que los objetos a los que hacen referencia los punteros siguen siendo válidos mientras el vector tiene una referencia a ellos (los punteros inteligentes que utilizan el lenguaje RAII resuelven el problema).

Alexander Gessler
fuente
También quisiera señalar que, si usa punteros sin procesar, ahora es responsable de limpiarlos. No hay una buena razón para hacer esto (no se me ocurre ninguna), siempre debes usar un puntero inteligente.
Ed S.
1
Dicho esto, no debe usar std :: auto_ptr en contenedores stl, para obtener más información: por qué está mal usar stdauto-ptr-with-standard-container
OriginalCliche
24
Desde C ++ 11, push_backrealizará un movimiento en lugar de una copia si el argumento es una referencia de valor. (Los objetos se pueden convertir en referencias de valor con std::move().)
emlai
2
@tuple_cat su comentario debe decir "si el argumento es un valor r". (Si el argumento es el nombre de una entidad declarada como referencia de valor, entonces el argumento es en realidad un valor y no se moverá de) - verifique mi edición con la respuesta de "Karl Nicoll" que cometió ese error inicialmente
MM
Hay una respuesta a continuación, pero para dejarlo claro: dado que C ++ 11 también se usa emplace_backpara evitar cualquier copia o movimiento (construir el objeto en el lugar proporcionado por el contenedor).
Wormer
34

Sí, std::vectoralmacena copias. ¿Cómo debe vectorsaber cuáles son los tiempos de vida esperados de sus objetos?

Si desea transferir o compartir la propiedad de los objetos, use punteros, posiblemente punteros inteligentes como shared_ptr(que se encuentran en Boost o TR1 ) para facilitar la administración de recursos.

Georg Fritzsche
fuente
3
aprende a usar shared_ptr: hacen exactamente lo que quieres. Mi idioma favorito es typedef boost :: shared_ptr <Foo> FooPtr; Luego haga contenedores de FooPtrs
pm100
3
@ pm100 - ¿Lo sabes boost::ptr_vector?
Manuel
2
También me gusta usar class Foo { typedef boost::shared_ptr<Foo> ptr; };para escribir Foo::ptr.
Rupert Jones
2
@ pm100 - shared_ptrno es exactamente disparar y olvidar. Consulte stackoverflow.com/questions/327573 y stackoverflow.com/questions/701456
Daniel Earwicker
2
shared_ptr es bueno si ha compartido la propiedad, pero generalmente se usa en exceso. unique_ptr o boost scoped_ptr tienen mucho más sentido cuando la propiedad es clara.
Nemanja Trifunovic
28

Desde C ++ 11 en adelante, todos los contenedores estándar ( std::vector, std::map, etc) de soporte se mueven semántica, lo que significa que ahora puede pasar a rvalues contenedores estándar y evitar una copia:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

Alternativamente, puede usar varios punteros inteligentes para obtener el mismo efecto:

std::unique_ptr ejemplo

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr ejemplo

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.
Karl Nicoll
fuente
2
Tenga en cuenta que std::make_unique(molestamente) está disponible solo en C ++ 14 y superior. Asegúrese de decirle a su compilador que establezca su conformidad estándar en consecuencia si desea compilar estos ejemplos.
Laryx Decidua
En 5a puedes usar auto pFoo =para evitar la repetición; y todos los std::stringmoldes se pueden eliminar (hay una conversión implícita de literales de cadena a std::string)
MM
2
@ user465139 make_uniquese puede implementar fácilmente en C ++ 11, por lo que es solo una ligera molestia para alguien atrapado con un compilador de C ++ 11
MM
1
@MM: De hecho. Aquí está la implementación del libro de texto:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua
1
@Anakin: Sí, deberían hacerlo, pero solo si copia. Si usa std::move()con std::shared_ptr, el puntero compartido original podría cambiar su puntero ya que la propiedad se pasó al vector. Ver aquí: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll
15

std :: vector siempre hace una copia de lo que se está almacenando en el vector.

Si mantiene un vector de punteros, hará una copia del puntero, pero no la instancia a la que apunta el puntero. Si se trata de objetos grandes, siempre puede (y probablemente debería) usar un vector de punteros. A menudo, usar un vector de punteros inteligentes de un tipo apropiado es bueno para fines de seguridad, ya que manejar la vida útil de los objetos y la administración de la memoria puede ser complicado de lo contrario.

Reed Copsey
fuente
3
no depende del tipo Siempre hace una copia. Si es un puntero se hace una copia del puntero
pm100
Los dos tienen razón. Técnicamente, sí, siempre hace una copia. Prácticamente, si le pasa un puntero al objeto, copia el puntero, no el objeto. De forma segura, debe usar un puntero inteligente apropiado.
Steven Sudit
1
Sí, siempre está copiando. Sin embargo, el "objeto" al que se refiere el OP es muy probablemente una clase o estructura, por lo que me refería a si copiar el "Objeto" depende de la definición. Aunque mal redactado.
Reed Copsey
3

Std :: vector no solo hace una copia de lo que está presionando, sino que la definición de la colección establece que lo hará, y que no puede usar objetos sin la semántica de copia correcta dentro de un vector. Entonces, por ejemplo, no usa auto_ptr en un vector.

Liz Albin
fuente
2

Relevante en C ++ 11 es la emplacefamilia de funciones miembro, que le permiten transferir la propiedad de los objetos moviéndolos a contenedores.

El modismo de uso se vería así

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

El movimiento para el objeto lvalue es importante ya que de lo contrario se reenviaría como referencia o referencia constante y no se llamaría al constructor de movimiento.

LemonPi
fuente
0

si no quieres las copias; entonces la mejor manera es usar un vector puntero (u otra estructura que sirva para el mismo objetivo). si quieres las copias; usar directamente push_back (). No tienes otra opción.

rahmivolkan
fuente
1
Una nota sobre los vectores de puntero: vector <shared_ptr <obj>> es mucho más seguro que el vector <obj *> y shared_ptr es parte del estándar a partir del año pasado.
rich.e
-1

¿Por qué tomó tanta investigación valgrind para descubrir esto? Solo demuéstralo a ti mismo con un código simple, por ejemplo

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

Si se imprime "hello world", el objeto debe haberse copiado

NexusSquared
fuente
44
Esto no constituye una prueba. Si el objeto no fue copiado, su última declaración sería un comportamiento indefinido y podría imprimir hola.
Mat
44
la prueba correcta sería modificar uno de los dos después de la inserción. Si fueran el mismo objeto (si el vector almacenara una referencia), ambos serían modificados.
Francesco Dondi