2017-10-11 44 views
0

我想了解为什么下面的C++程序输出它的功能。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; 
} 

这是我没有料到它的工作和我,它预计打印:

Test test1 = getTest(); 

在这里,我期待这样的事情发生:在getTest内部测试实例与构造函数与创建因此没有参数:cout < <构造函数;然后这个值被返回,并且用'='分配给test1,在这种情况下这将是'copy ctor',因此也是'copy ctor';

cout << test1 << endl; 

在这里,我希望COUT < < “从测试你好”;曲子的重载“< <”

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

在这里,我在等一个实例被创建并被推入VEC(1)与没有参数构造函数,以便COUT < <“构造” < < ENDL;

然后我期待TEST1和VEC(1)走出去的范围在程序,以便2X“COUT < <‘析构函数的结束’;”

所以,总体来说我的期望是这样的:

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

然而该方案的实际工作输出是这样的:

constructor 
Hello from test 
constructor 
copy constructor 
Destructor 
Destructor 
Destructor 

这是从我的期待:)不同。

在这些我想我能理解我最后得到的额外的析构函数。我想当函数getTest()返回一个赋值给test1的值时,该值也会在程序结束时被销毁,这就是为什么额外的析构函数。或者至少这是我的想法。如果我错了,请纠正我。

此外,我不明白为什么我没有得到一个“清点< <‘拷贝构造函数’”后“COUT < <‘构造’,”第一个。不是测试test1 = getTest();'呼叫复制ctor?

如果可能的话,请帮我理解这个程序的流程,这样我就能理解它为什么输出它所做的事情,并且更好地理解C++中的OOP。谢谢你的阅读。

+1

那么偏差之一是构造函数是一个构造函数是一个构造函数。即使它是一个复制构造函数,它也用于构造对象,因此必须具有相应的析构函数调用。这就是为什么你的三个构造函数调用有三个析构函数调用。 –

+1

由于[RVO](https://en.wikipedia.org/wiki/Return_value_optimization),您预期的第一个拷贝构造函数可能会被删除 – Default

+1

顺便说一句,与您的问题无关,但您的任务中存在一个错误运营商。您在不释放旧内存的情况下分配新内存。实际上,由于构造函数已经分配了内存,所以在赋值操作符中根本就没有必要进行分配。 –

回答

0

此行为是由于copy elision。从标准§[class.copy]¶31:

当满足特定条件时,一种实现被允许省略 一个类对象的复制/移动结构,即使选择用于拷贝构造 /移动操作和/或对象的析构函数有副作用。在这种情况下,实现将忽略的复制/移动操作的源和目标视为简单地引用同一对象的两种不同的方式,并且销毁该对象发生在两个对象的晚些时候如果没有优化, 将被销毁。 复制/移动操作的此省音,称为复制省略,被允许在 下列情况下(其可以被组合以消除多个 份):
[...]
- 当临时类对象没有被绑定到引用 (12.2)将被复制/移动到具有相同 cv-不合格类型的类对象,复制/移动操作可以被 直接构建到 的目标中省略复制/移动 [...]

在这种情况下,Test test1 = getTest()满足我上面引用的标准。将在getTest中创建的临时对象Test未绑定到引用。因此可以直接在getTest的返回值中构建。这种优化也被称为返回值优化或RVO。另外,由于返回值getTest(本身就是一个临时对象),没有被绑定到一个引用,它可以直接构造成test1

如果是vec.push_back(Test());,则必须将您创建的临时对象Test复制或移动到vector的内部存储器中。由于您创建的临时对象在传递到push_back时被绑定到引用,并且标准中列出的其他任何情况都不适用,因此在这种情况下不可能有复制elision。这意味着必须创建两个Test对象:一个(您的临时)使用其默认构造函数构造,另一个(vector的副本)由复制构造函数创建。

+0

谢谢。很好的解释。 – Kennedy

0

所以这里是程序的实际输出。我禁用了编译器优化和返回值优化。以下是程序的输出:

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

让我们打破这一点。第一个构造函数,复制和析构函数调用都与getTest()方法有关。当您通过Test()这个输出constructor创建对象的实例时,最初调用构造函数。这个值然后需要被返回,因为这全部编译成汇编对象需要放在堆栈上,特别是需要放在堆栈上的函数调用之前的位置上(我可能在之前或之前都是错误的但是在为返回值保留位置之后)。所以实例被复制到堆栈上的那个位置,输出copy constructor。这会导致函数完成并调用创建的原始实例的析构函数。

接下来是Test test1 = getTest();,则在从该函数调用所保留的位置上的实例被复制到变量test1导致copy constructor然后在保留位置上的实例销毁输出Destructor

然后显示Hello from test,由于流操作符与引用一起工作,因此不会复制任何内容。所以没有构造函数或析构函数被调用。

最后constructorvec.push_back(Test());被称为Test()时,输出呼叫。创建此实例,然后向量类在push_back调用期间将该实例复制到向量类的数组中的某个位置。剩余的三个析构函数被调用为test1,在push_back期间创建的实例以及存储在向量类中的实例。

我希望这有助于。

+0

谢谢。非常明确的解释。我知道有一些关于实例的东西是作为局部变量创建的,当函数超出范围时(当函数结束时)必须被销毁,但是我感到困惑,因为我并不认为由于该优化变成了析构函数可能。我得去读更多关于这个。再次感谢你。 – Kennedy