I have an object that internally stores a pretty heavy collection of objects. This collection is populated on demand, i.e. if the collection is not needed it will not be populated for life:
class Objeto
{
std::vector<int> coleccion;
// Solo una factoría puede crear objetos de este tipo
Objeto();
void RellenarColeccion()
{ /* ... */ }
public:
// No se admiten copias
Objeto(Objeto const&) = delete;
// Constructor move
Objeto(Objeto &&) = default;
std::vector<int> const& Coleccion()
{
if( coleccion.empty() )
RellenarColeccion();
return coleccion;
}
};
The class works perfectly. Returning a reference to the collection prevents copies and the program ends up working reasonably well... until the need arises to copy the collection from the object.
This need arises in a particular situation in which the only thing that matters about the object is its internal collection:
Objeto NuevoObjeto() // Función que emula la factoría
{ return Objeto; }
int main()
{
// Se invoca la sintaxis move y la copia es bastante liviana
Objeto o1 = NuevoObjeto();
// Obtenemos la colección vía referencia. Evitamos la copia
auto& v1 = o1.Coleccion();
// El problema lo tenemos cuando del objeto solo queremos su coleccion
// La copia es demasiado pesada
auto v2 = NuevoObjeto().Coleccion();
// Y no nos podemos quedar con la referencia
auto& v3 = NuevoObjeto().Coleccion(); // Referencia a objeto destruido
}
Is there a solution that allows you to take advantage of the move syntax by making changes only to Objeto
? The expected would be the following:
int main()
{
// caso 1: se accede por referencia
Objeto o1 = NuevoObjeto();
auto& v1 = o1.Coleccion();
// caso 2: Se llama a la sintaxis move para evitar la copia del vector
auto v2 = NuevoObjeto().Coleccion();
}
It is possible to mark a member function to be called when it is applied to a temporary object (or right-value type object) by appending it
&&
to the end of the function signature, this is known as " reference qualifiers " or " reference to right-value for*this
", if you use these functions to yourObjeto
:In the version that uses a right-value reference for
*this
, we usestd::move
to transform thecoleccion
internal of the temporary object into a right-value vector, which will be used to construct thestd::vector<int>
result that, being given a right-value, will move the content instead to copy it 1 . To see if this works as you need, I've made a few changes to your example class:I have added some messages on the construction and destruction of
Objeto
and changed the type of the object contained in the collection to this:With these changes, if we execute this code in
main
:We have this output 2 :
We see that the first
Objeto
(0x000000000001
) creates a collection with the element0x0000001
and that collection is read through the functionColeccion
that is not applied on temporary, we do not see that the element0x0000001
is destroyed.In the second part a
Objeto
temporary (0x000000000002
) is created that contains the element0x0000002
and immediately afterwards the is destroyedObjeto
without the element (0x0000002
) being destroyed, that means that the collection has been moved, we can also see that the version ofColeccion
that has been called calls about storms.After the message
Después
, the destructors of the temporarily created collection element are calledObjeto
from the factory (0x0000002
), then the destructor of the firstObjeto
(0x000000000001
) created is called, which calls the destructor of its only element (0x0000001
).This behavior shows us that there has been no copy of the collection obtained from the temporary (if there had been a copy, an additional construction-destruction of collection elements would be seen).
You can see the code working .En Wandbox 三へ( へ՞ਊ ՞)へ ハッハッ
1 Warning, using
std::move
in an instructionreturn
prevents optimization of the return value .2 I have edited the memory addresses, for clarity and because in different runs they are different values.
Ok, what you have is the following:
As you can see, there you return a list, so a copy will be made when creating v2. What you can do is return a pointer to that collection, so you're just passing an address instead of a whole list.
You could do this by returning a
void *
and then casting your std::vector:The modification I propose to your Collection() method would look like this:
So what you do is work with references and not copies of data. Hope this can help you.