简体   繁体   English

C ++:执行程序(构造函数,析构函数,赋值运算符等)

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

I'm trying to understand why the following C++ program outputs what it does. 我试图理解为什么以下C ++程序输出其功能。

#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; 我期望在这里发生这种情况:在getTest内,使用ctor创建了一个Test实例,因此没有任何参数:cout <<构造函数; 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'; 然后,该值被返回并用'='分配给test1,在这种情况下,该值将是'copy ctor',因此也是cout <<'copy ctor';

cout << test1 << endl;

Here I expected cout << "Hello from test"; 在这里,我期望cout <<“来自测试的Hello”; cuz of the overloaded '<<' 重载的'<<'的cuz

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; 在这里,我期望创建一个实例,并使用no参数ctor将其推入vec(1),所以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"; " 然后我期望test1和vec(1)在程序结束时超出范围,所以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. 我想当函数getTest()返回一个分配给test1的值时,该值也会在程序结尾处被销毁,这就是为什么需要额外的析构函数的原因。 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";'. 我也不明白为什么我在第一个'cout <<“ Constructor”;之后没有得到'cout <<“ Copy ctor”'。 Isn't Test test1 = getTest();' 不是测试test1 = getTest();' a call to copy ctor? 呼叫复制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++. 另外,如果可能的话,请帮助我理解该程序的流程,以便我理解为什么它输出其功能并更好地理解c ++中的OOP。 Thank you for reading. 感谢您的阅读。

This behavior is due to copy elision . 此行为是由于复制省略 From the standard §[class.copy]¶31: 从标准§[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 [...] —当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv-unqualtype类型的类对象时,可以通过将临时对象直接构造为以下形式来省略复制/移动操作:省略的复制/移动目标[...]

In this case, Test test1 = getTest() fulfills the criterion I've quoted above. 在这种情况下, Test test1 = getTest()满足我上面引用的条件。 The temporary Test object that would be created in getTest is not bound to a reference. 将在getTest创建的临时Test对象未绑定到引用。 It can therefore be constructed directly in the return value of getTest . 因此,可以直接在getTest的返回值中getTest This optimization is also referred to as Return Value Optimization, or RVO. 此优化也称为返回值优化或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 . 同样,由于getTest的返回值(本身是一个临时对象)未绑定到引用,因此可以直接将其构造到test1

In the case of vec.push_back(Test()); 对于vec.push_back(Test()); , the temporary Test object you create must be copied or moved into the vector 's internal storage. ,您创建的临时Test对象必须复制或移动到vector的内部存储中。 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. 在这种情况下,无法进行复制省略,因为您创建的临时对象在传递给push_back时将绑定到引用,并且标准中列出的其他情况均不适用。 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. 这意味着必须创建两个Test对象:一个(使用您的临时对象)使用其默认构造函数构造,以及另一个(副本的vector )由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. 第一个构造函数,复制和析构函数调用均与getTest()方法相关。 The constructor is initially called when you create a instance of the object by Test() this outputs constructor . 当您通过Test()创建对象实例时,最初会调用constructor这将输出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 . 因此,实例被复制到堆栈上的那个位置,并输出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(); 接下来是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 . 然后,将函数调用中保留位置处的实例复制到变量test1从而生成copy constructor ,然后保留位置处的实例将销毁,并输出Destructor

Then the Hello from test is displayed and since the stream operator works with references, nothing is copied. 然后显示Hello from testHello from test ,由于流运算符使用引用,因此不会复制任何内容。 So no constructor or destructors are called. 因此,不会调用任何构造函数或析构函数。

The last constructor call is output when vec.push_back(Test()); vec.push_back(Test());时输出最后一个constructor调用vec.push_back(Test()); is called specifically Test() . 专门称为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. 创建此实例后,向量类将在push_back调用期间将该实例复制到向量类的数组中的某个位置。 The remaining three destructors are called for the test1 , the instance created during the push_back and the instance stored in the vector class. 剩下的三个析构函数将调用test1 ,在push_back期间创建的实例以及存储在vector类中的实例。

I hope this helped. 希望对您有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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