2

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?

Leonardo Raele
  • 2,400
  • 2
  • 28
  • 32
  • 4
    Yes, make sure you read up on RVO. No, don't use a smart pointer if you are after performance (the allocation may cost a lot more than any possible copy). And you can often just require the caller to allocate and pass in a non-const reference (ugly but effective). – Keith Jul 26 '12 at 04:40
  • 1
    @Keith but then assigning to the parameter is comparable to copying it... – Luchian Grigore Jul 26 '12 at 04:41
  • You can't really optimize this unless the object has external resources. And if it does, C++11 will take care that a simple return will move the local object. If the object is flat (aka no pointers / handles to external resources), I'd simply take the copy if it comes, it shouldn't cost much (you don't want to have big flat objects, the fear of inefficient returns comes only with types that have external resources). – Xeo Jul 26 '12 at 04:47
  • @Luchian: If it has external resources, it could have a method to "steal" from another object, even in C++03 without move-semantics by explicitly requesting it (like `result_ref.take_from(other)`). Or you could even just use that `result_ref` to do everything you want. – Xeo Jul 26 '12 at 04:49
  • 4
    Premature optimization. Uses the easiest most maintainable method (the first version). Only try and optimize if it is slow **and you can measure it** so that you can compare it with an attempted optimizations. If you do this I bet the shared pointer is probably slower in a lot of cases. – Martin York Jul 26 '12 at 05:05

3 Answers3

6

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.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
2

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;
}
  • 1
    if NRVO is being done the move for return is not necessary. As it was built in place at the destination `E` and therefore need not be copied back. – Martin York Jul 26 '12 at 05:19
  • The `if (V)` in the destructor is not necessary. It is perfectly fine to `delete[] NULL`. Also, your class is missing a copy assignment operator and a move assignment operator. Also, moving from automatic objects is implicit, see [here](http://stackoverflow.com/a/11540204/) under "Moving out of functions". – fredoverflow Jul 26 '12 at 06:38
1

Source: http://blogs.msdn.com/b/slippman/archive/2004/02/03/66739.aspx

Compiler optimisers are smart enough to figure out what's going on, so when optimising it will internally rewrite the call to:

void CallingFunction()
{
    // Call to createObject() gets eliminated through transformation
    // since in this case it's only code was the initialization
    MyObject obj(1, 2, 3);

    // ... do stuff
}
Keldon Alleyne
  • 2,103
  • 16
  • 23