简体   繁体   中英

C++: Execution of programm(constructors, destructors, assignment operators, etc)

I'm trying to understand why the following C++ program outputs what it does.

#include <iostream>
#include <vector>
#include <initializer_list>
#include <memory.h>
using namespace std;


// Constructors and memory

class Test{
    static const int SIZE = 100;
    int *_pBuffer;

public:
    Test(){
        cout << "constructor" << endl;
        _pBuffer = new int[SIZE]{}; // allocated memory for int[size] and initialised with 'size' 0's

    }

    Test(int i){
        cout << "parameterized constructor" << endl;
        _pBuffer = new int[SIZE]{};

        for(int i=0; i<SIZE; i++)
        {
            _pBuffer[i] = 7*i;
        }
    }

    Test(const Test& other) // in the copy constructor we...
    {
        cout << "copy constructor" << endl;
        _pBuffer = new int[SIZE]{}; // allocate the bytes then copy them from the 'other'
        memcpy(_pBuffer, other._pBuffer, SIZE*sizeof(int));

    }

    Test &operator=(const Test &other){
        cout << "assignment" << endl;

        _pBuffer = new int[SIZE]{}; // allocate the bytes then copy them from the 'other'
        memcpy(_pBuffer, other._pBuffer, SIZE*sizeof(int));

        return *this;
    }

    ~Test(){
        cout << "Destructor" << endl;

        delete [] _pBuffer;
    }

};

ostream &operator<<(ostream &out, const Test &test)
    {
        out << "Hello from test";
        return out;
    }

Test getTest()
{
    return Test();
}


int main() {

    Test test1 = getTest(); // object gets created with default constructor =>
    cout << test1 << endl;

    vector<Test> vec;
    vec.push_back(Test());

    return 0;
}

This is how I expected it to work and what I expected it to print:

Test test1 = getTest(); 

Here I was expecting this to happen: Inside the getTest a Test instance is created with the ctor with no parameters therefore: cout << constructor; Then this value is returned and assigned to test1 with the '=' which in this case would be the 'copy ctor' therefore also cout << 'copy ctor';

cout << test1 << endl;

Here I expected cout << "Hello from test"; cuz of the overloaded '<<'

vector<Test> vec;
vec.push_back(Test());

And here I was expecting an instance to be created and pushed into vec(1) with the no parameter ctor so cout << "Constructor" << endl;

Then I was expecting the test1 and vec(1) to go out of scope at the end of the program so 2x "cout << "destructor"; "

So overall my expectations was this:

cout << constructor;
cout << copy constructor;
cout << hello from test;
cout << constructor;
cout << destructor;
cout << destructor;

However the actuall output of the program is this:

constructor
Hello from test
constructor
copy constructor
Destructor
Destructor
Destructor

which is different from my expectations :).

Out of those I guess I can understand the extra destructor I get in the end. I suppose when the function getTest() returns a value which is assigned to test1 that value also gets destroyed at the end of the program so that's why the extra destructor. Or at least that's what I think. Please correct me if I'm wrong.

Also I don't understand why I don't get a 'cout << "Copy ctor"' after the first 'cout << "Constructor";'. Isn't Test test1 = getTest();' a call to copy ctor?

Also if possible please help me understand the flow of this program so I can understand why it outputs what it does and get a better understanding of OOP in c++. Thank you for reading.

This behavior is due to copy elision . From the standard §[class.copy]¶31:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision , is permitted in the following circumstances (which may be combined to eliminate multiple copies):
[...]
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move [...]

In this case, Test test1 = getTest() fulfills the criterion I've quoted above. The temporary Test object that would be created in getTest is not bound to a reference. It can therefore be constructed directly in the return value of getTest . This optimization is also referred to as Return Value Optimization, or RVO. Also, since the return value of getTest (itself a temporary object), is not bound to a reference, it can be constructed directly into test1 .

In the case of vec.push_back(Test()); , the temporary Test object you create must be copied or moved into the vector 's internal storage. No copy elision is possible in this case since the temporary object you create gets bound to a reference when passed to push_back and none of the other situations listed in the standard apply. That means that two Test objects must be created: one (your temporary) constructed using its default constructor, and another (the vector 's copy) created by copy-constructor.

So here is the actual output of the program. I have disabled compiler optimisation and return value optimisation. The following is the output of the program

constructor
copy constructor
Destructor
copy constructor
Destructor
Hello from test
constructor
copy constructor
Destructor
Destructor
Destructor

Lets break this down. The first constructor, copy and destructor calls are all related to the getTest() method. The constructor is initially called when you create a instance of the object by Test() this outputs constructor . This value then needs to be returned, since this is all compiled down to assembly object needs to be place on the stack, in particular in needs to be place on the position before the function call on the stack(I might be wrong either before or after however a position is reserved for the return value). So the instance is copied to that position on the stack outputting the copy constructor . This results in the function completing and calling the destructor of the original instance that was created.

Next is the Test test1 = getTest(); , the instance at the reserved position from the function call is then copied to the variable test1 resulting in copy constructor and then the instance in the reserved position is destroyed outputting Destructor .

Then the Hello from test is displayed and since the stream operator works with references, nothing is copied. So no constructor or destructors are called.

The last constructor call is output when vec.push_back(Test()); is called specifically Test() . This instance is created and then the vector class copies that instance to a position in the vector class's array during the push_back call. The remaining three destructors are called for the test1 , the instance created during the push_back and the instance stored in the vector class.

I hope this helped.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM