3

I was looking to understand how vector is implemented in C++. There was a previous question that asked this, and so I took a look at it, and I have a small question. Assuming the implementation in the linked question is correct, let's look at this code:

int main(){
    Vector<int> test2 = test_Vector();
    cout << test2[0] << endl;
    return 0;
}


// below is NOT the STL vector object, but the one in the linked question,
// in which the asker tries to implement STL vector himself/herself 
Vector<int> test_Vector(){
    Vector<int> test;
    test.push_back(5);
    return test;
}

As I understand it, the test Vector object is created locally, so when the test_Vector method returns, the local object goes out of scope, thereby calling the destructor and delete-ing the dynamic array. Since the code actually works and 5 is printed, I guess I'm wrong. What's the right explanation?

Community
  • 1
  • 1
naxchange
  • 893
  • 1
  • 11
  • 24
  • 2
    See: [Return Value Optimization (RVO)](http://en.wikipedia.org/wiki/Return_value_optimization) – Andrew Tomazos Mar 20 '13 at 23:23
  • The point about RVO is extremely important: often programmers who are new to C++ find complicated ways not to return objects by value out of fear of expensive copies. Quite often, there is no copying at all. – juanchopanza Mar 20 '13 at 23:25

3 Answers3

3

You are right, but you're missing one important thing.

Because you're returning a Vector<int>, you should think of it as being copied. This would normally invoke the copy constructor, which copies test into a new instance of Vector<int>. The copy constructor is implemented in the linked question as:

template<class T>
Vector<T>::Vector(const Vector<T> & v)
{
    my_size = v.my_size;
    my_capacity = v.my_capacity;
    buffer = new T[my_size];  
    for (int i = 0; i < my_size; i++)
        buffer[i] = v.buffer[i];  
}

Note that the copy constructor might not be invoked due to Return Value Optimization (see hair-splitting in comments below). Compilers are allowed to optimize the copy away in many cases, and the C++ standard allows for the fact that this optimization may change program behaviour.

Whether the object is copied, or RVO is applied, you should end up with the same thing. The optimization should not ruin your object provided you follow normal object-oriented practices.

You should always think of function return values being passed by value (ie copied) regardless of type, and then consider that your compiler is probably doing RVO. It's important not to forget the Rule of Three (or Four, or Five).

Community
  • 1
  • 1
paddy
  • 60,864
  • 6
  • 61
  • 103
  • 1
    Of course, the compiler could optimize the temporary object away via RVO, so if you have logic in the copy constructor it could get skipped. :) – Joe Mar 20 '13 at 23:15
  • 1
    Please change "it will invoke the copy constructor" to "it may invoke the copy constructor" – Slava Mar 20 '13 at 23:16
  • Ok, I guess that explains it, but forgive me, I'm still somewhat of a beginner; why is the copy constructor invoked when the method returns? Is that something I just have to know? – naxchange Mar 20 '13 at 23:17
  • 1
    That's right, but I think it's hard for the compiler to do this and know it's doing the right thing when pointers are involved. That is one reason why *move-semantics* were introduced in C++11. – paddy Mar 20 '13 at 23:18
  • It does not need to know, compiler can eliminate copy ctor call even if copy ctor has side effects. – Slava Mar 20 '13 at 23:19
  • 1
    @paddy please read about RVO, your remark about inline is irrelevant as well. – Slava Mar 20 '13 at 23:22
  • @naxchange When you return any type from a function think of it as being passed by value. So if you have an `int` from a local temporary, you can theoretically expect that `int` to be copied into a new int. Same with a pointer (remember a pointer is just a number). And it's the same with objects. This is not always going to be the case. The compiler may optimize the copy out, or the variable might never exist in memory (only in registers). But it's okay to think of it as being a copy, and even expect it to be a copy. – paddy Mar 20 '13 at 23:25
  • @slava By my understanding, the standard *allows* for RVO but doesn't *require* it. – paddy Mar 20 '13 at 23:27
  • @paddy correct. But it might prove difficult to find a compiler that doesn't perform RVO in OP's code example. – juanchopanza Mar 20 '13 at 23:30
  • @juanchopanza That's okay... It's surely better to think of it as being a copy and then learn later that it is being optimized. You still have to code with copy semantics (Rule of Three) in mind... Or move semantics (Rule of Four or Five). So if you understand that the code looks like a copy, I think that's more important than forgetting your copy constructor and getting undefined behaviour *sometimes*. – paddy Mar 20 '13 at 23:36
  • @paddy right, but what does it change? Statement still should be "may call copy ctor" and it is irrelevant if raw pointers used or not, and if copy ctor inlined or not. – Slava Mar 20 '13 at 23:36
  • Following this discussion, I have edited again. I hope everyone is satisfied with the answer. Cheers. – paddy Mar 20 '13 at 23:43
1

He provided a public copy constructor (technically, he didn't make the copy constructor private), so the standard C++ logic kicks in and makes a copy of the object to return. The new object is local to main. Inside the copy constructor, new memory is malloced and the data copied over. If he hadn't provided a copy constructor, it would have accessed invalid memory (trying to access the memory from the pointer which has been freed)

Dave
  • 44,275
  • 12
  • 65
  • 105
  • 1
    In real life, it is extremely likely that the copy would be elided via return value optimization. – juanchopanza Mar 20 '13 at 23:21
  • 1
    @juanchopanza true but that isn't the way you should think about it, because that's an optimisation not a feature, and if the copy constructor was malformed, you would get varying behaviour depending on the optimisation. – Dave Mar 20 '13 at 23:26
  • 1
    Of course, but the other side of the coin is that you also have to expect that there may be (in the case above, definitely) be no call to the copy constructor, because this is an optimization that is allowed to change the observable behaviour of the program. – juanchopanza Mar 20 '13 at 23:28
1

When test is returned its copy constructor is invoked (in theory) which allocates a new chunk of memory and copies the contents of test. The original test on test_Vectors stack is destructed (in theory) and test2 gets assigned the copy, which means the assignment operator will be invoked (in theory) which will again allocate memory and copy the data from the temporary object that was created when returning. The temporary object returned from test_Vector then has its destructor called (in theory).

As you can see, without compiler optimizations this is hell :) The "in theory"s can be skipped if you don't do anything too baroque and the compiler is smart and your situation is this simple. See this for some updates to the story as of C++11.

Jacob Parker
  • 2,546
  • 18
  • 31