Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to optimize object return in C++?

Consider the code bellow.

// Consider that MyObject receives three integers in it's constructor.
MyObject createObject()
{
    MyObject result(1, 2, 3);
    return result;
}

As long as I know, when you return an object in C++(like in this function), the compiler will create a new object(as a local variable) in the stack for the client. In this exemple, it will be a new MyObject object that will be created using the copy constructor with result as parameter. This implies in redundant processing for the machine: the object result will be created in the stack and then a second object will be created and returned. An alternative to avoid this is creating the object dynamically(not in stack) and then return it's pointer. But it will cause memory leak or force the client to delete the object after use. So, the solution I found was to use an smart pointer, like this:

// Consider that MyObject receives three integers in it's constructor.
boost::smart_ptr<MyObject> createObject()
{
    boost::smart_ptr<MyObject> result(new MyObject(1, 2, 3));
    return result;
}

This works, but the smart pointer still is an object that will be recreated(even if the cost is low becouse, basically, it holds just a pointer). I was guessing if there's a way to do this a more easily; or if there wasn't a way already implemented by the compiler to optimize this work or something alike. Is there a syntax to say to the compiler to do what I want?

like image 826
Leonardo Raele Avatar asked Jul 13 '26 14:07

Leonardo Raele


2 Answers

Profile your code first, I'm 99% sure there's no overhead from copying.

NRVO exists and will most probably kick in in this case.

If it weren't for copy elision, returning collections (like std::vector and the likes) would cause massive problems.

when you return an object in C++(like in this function), the compiler will create a new object(as a local variable) in the stack for the client.

Not really. It will probably be created directly in the calling context, precisely to prevent the extra copy.

like image 180
Luchian Grigore Avatar answered Jul 15 '26 04:07

Luchian Grigore


If you are willing to use C++11, you can use "move semantics". Let me explain by first giving an example:

class expensive {
public:
     expensive() : n(0), V(NULL) {}
     expensive(int _n) {
         n = _n;
         V = new int[_n];
         for (int i = 0; i < n; ++i) V[i] = i*i;
     }

     expensive(const expensive& rhs) { // copy constructor
          n = rhs.n;
          V = new int[n];
          for (int i = 0; i < n; ++i) V[i] = rhs.V[i];
     }
     expensive(expensive&& rhs) { //move constructor
          n = rhs.n;
          V = rhs.V;
          rhs.n = -1;
          rhs.V = NULL;
          printf("Moving\n");
     }
     ~expensive() {
          if (n == -1) printf("Destroying 'moved' instance\n");
          if (V) delete [] V;
     }
private:
     int *V;
     int n;
};
expensive f(int x) {
    expensive temp(50);
    expensive temp2(temp); // Copy temp to temp2, both are valid now
    expensive temp3(std::move(temp)); // Move temp to temp3, temp is not valid anymore
    return std::move(temp2); // move temp2 to the return 
}
int main() {
    expensive E = f(30);
    return 0;
}

The output of this program is:

Moving
Moving
Destroying 'moved' instance
Destroying 'moved' instance

All the normal STL containers support move semantics. Also, std::swap uses it (and therefore, std::sort as well).

EDIT: As was noted, if NRVO is being used, the last std::move is unnecessary. Let me make a more complex example. This is a toy example, but it should show my point.

class expensive {
    ... // Same definition as before
}
expensive g(int x) {
    vector<expensive> V;
    V.reserve(2*x);
    for (int i = 1; i <= x; ++i)
         V.push_back(expensive(i)); // Here the objects are moved to the vector
    for (int i = 1; i <= x; ++i)
         V.emplace_back(i); // Here the objects are constructed directly in the vector
    return std::move(V[x/2]); // V[x/2] is moved to the answer
}
int main() {
    expensive x(g(2)); // 3 objects should be moved, 2 in push_back and one on the return
    return 0;
}
like image 22
Daniel Fleischman Avatar answered Jul 15 '26 04:07

Daniel Fleischman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!