Within this article I want to think about the question how we can move objects between different scopes without the need of expensive copying and without the need to use an error-prone pointer handling. To analyze this topic, we want to write a function which returns a large object. Returning an object of a small built-in type, like an integer value, carries little to no overhead. Returning a larger object of class type may require more expensive copying from one memory location to another.
To keep it simple I will use a vector-object within the example of this article. But the shown principles will be the same if you use your own classes.
Return a pointer
The traditional approach returning a large object is to use a pointer. Therefore, we will start with an example application implementing this approach.
std::vector* CreateInstance() { std::vector* instance = new std::vector(); //...fill vector with data return instance; }; int _tmain(int argc, _TCHAR* argv[]) { std::vector* data = CreateInstance(); //...use data class delete data; return 0; }
This works fine but comes with a big disadvantage: the wild pointer will undermine the resource safety concept of C++. This will lead to typical issues memory leaks.
Return a smart pointer
To improve the example application, we could use a smart pointer. This will eliminate the resource safety violation as we now follow the RAII principle (resource acquisition is initialization).
std::unique_ptr<std::vector> CreateInstance() { std::unique_ptr<std::vector> instance = std::make_unique<std::vector>(); //...fill vector with data return instance; }; int _tmain(int argc, _TCHAR* argv[]) { std::unique_ptr<std::vector> data = CreateInstance(); //...use data class return 0; }
This implementation is much better compared to the first one. But it also raises the question: Why use a pointer at all? Often, I don’t want to use a pointer at all, even if it is a smart pointer. Pointers distracts from the conventional use of an object.
Return an object
What I want to do is using the object. I want to implement a function which creates and returns the object without the need of using pointers. The following source code shows the adapted example.
std::vector CreateInstance() { std::vector instance = std::vector(); //...fill vector with data return instance; }; int _tmain(int argc, _TCHAR* argv[]) { std::vector data = CreateInstance(); //...use data class return 0; }
By default, this copies the elements of “instance” into “data”. And of course, such a copy is expensive for large objects. But since “instance” is just about to be destroyed and the memory holding its elements is to be freed, there is no need to copy this object. Instead it is possible to steal the elements. C++11 directly supports this “stealing mechanism” by using move constructors. Therefore, with C++11 this implementation will create a cheap copy of the object as it simple moves the ownership of the elements. So, we don’t have to fear expensive copy mechanisms and can return the object directly, without the need of pointers.
Return value optimization
The shown move of the object will only work if the object contains a move constructor. This could be an explicitly implemented one or an implicitly declared one. Maybe you will come in a situation where you have to use an object which does not contain a move constructor. Which of the above implementation will now be the best one? Good news everyone: you can still return the object and don’t have to use pointers. This is possible because the compiler itself provides an optimization algorithm: the return value optimization. In case the compiler will find the code shown above, he can optimize it and change it to something like that:
void CreateInstance(std::vector* instance) { instance = new std::vector(); //...fill vector with data }; int _tmain(int argc, _TCHAR* argv[]) { std::vector data; CreateInstance(&data); //...use data class return 0; }
This optimization is called “Copy elision”. It omits copy- and move- constructors. Therefore, it will work in both cases: for objects with move constructor and for objects without move constructors. In one case, it omits the expensive copy constructor and in the other case it omits the cheap movement and may make it even a little cheaper.
Summary
Don’t fear to return large objects directly. This object movement is very cheap in case your object supports movement. Most objects contain the needed move constructor implicitly or you can explicitly implement one. Furthermore, in case your object does not contain a move constructor at all, the return value optimization of the compiler will eliminate expensive copy command.