复制构造函数和析构函数的奇怪调用

时间:2019-03-17 20:58:20

标签: c++

此问题与以下所有问题均不同:

由堆栈溢出建议。


假设您有这个简单的代码。

#include <iostream>
using namespace std;

class Int {

    private:

        int i_;

    public:

        Int(Int &&obj) : i_(obj.i_) { //move constructor
            print_address_change_(&obj);
            cout << i_ << " moved.\n";
        }

        Int(const Int &obj) : i_(obj.i_) { //copy constructor
            print_address_change_(&obj);
            cout << i_ << " copied.\n";
        }

        Int(int i) : i_(i) {
            print_address_();
            cout << i_ << " constructed.\n";
        }

        ~Int() {
            print_address_();
            cout << i_ << " destructed.\n";
        }

        void print_address_() const {
            cout << "(" << this << ") ";
        }

        void print_address_change_(const Int *p) const {
            cout << "(" << p << " -> " << this << ") ";
        }

        const Int operator * (const Int &rhs) const {
            return Int(i_ * rhs.i_);
        }

};

int main() {

    Int i(3);
    Int j(8);

    cout << "---\n";
    Int k = i * j;
    cout << "---\n";

}

结果(通过带有默认选项的g ++ 7.3.0)是这样。

(0x7ffd8e8d11bc) 3 constructed. //i
(0x7ffd8e8d11c0) 8 constructed. //j
---
(0x7ffd8e8d11c4) 24 constructed. //tmp
---
(0x7ffd8e8d11c4) 24 destructed. //k
(0x7ffd8e8d11c0) 8 destructed. //j
(0x7ffd8e8d11bc) 3 destructed. //i

好。有点奇怪,但是您可以说copy elision必须已经发生。因此,现在使用-fno-elide-constructors选项,您将获得以下结果。

(0x7ffd8f7693f8) 3 constructed. //i
(0x7ffd8f7693fc) 8 constructed. //j
---
(0x7ffd8f7693c4) 24 constructed. //tmp
(0x7ffd8f7693c4 -> 0x7ffd8f769404) 24 moved. //tmp -> ??? (WHY?)
(0x7ffd8f7693c4) 24 destructed. //tmp
(0x7ffd8f769404 -> 0x7ffd8f769400) 24 copied. //??? -> k (WHY?)
(0x7ffd8f769404) 24 destructed. //??? (WHY?)
---
(0x7ffd8f769400) 24 destructed. //k
(0x7ffd8f7693fc) 8 destructed. //j
(0x7ffd8f7693f8) 3 destructed. //i

这比我预期的多了三行(标记为“为什么”)。什么是????谁能告诉我那里发生了什么事?

2 个答案:

答案 0 :(得分:3)

operator*Int(i_ * rhs.i_)构造一个临时对象。它返回该对象,该对象在函数外部构造了另一个临时对象。该临时对象将被复制到k中。

答案 1 :(得分:1)

写时:

Int k = i * j;

您实际上正在做类似的事情:

Int tmp1(i.operator*(j));
Int k(tmp1);

也就是说,使用=进行初始化实际上是通过创建临时文件并进行复制构造来使用的。与直接初始化的Int k(i * j)Int k{i *j}进行比较。

在这种情况下,这并不是真的很重要,因为即使您编写Int k(i*j),在调用operator*的副本构造函数之前,仍需要一个临时文件来保存k的返回值。

operator*等效于:

Int tmp2(24);
return std::move(tmp2);

所以您的输出表示:

(0x7ffd8f7693f8) 3 constructed. //i
(0x7ffd8f7693fc) 8 constructed. //j
---
(0x7ffd8f7693c4) 24 constructed. //tmp2 inside operator*
(0x7ffd8f7693c4 -> 0x7ffd8f769404) 24 moved. //return value into tmp1
(0x7ffd8f7693c4) 24 destructed. //tmp2
(0x7ffd8f769404 -> 0x7ffd8f769400) 24 copied. //k = tmp1
(0x7ffd8f769404) 24 destructed. //tmp1
---
(0x7ffd8f769400) 24 destructed. //k
(0x7ffd8f7693fc) 8 destructed. //j
(0x7ffd8f7693f8) 3 destructed. //i