【问题标题】:Strange call of copy constructor and destructor [duplicate]复制构造函数和析构函数的奇怪调用[重复]
【发布时间】:2019-03-17 20:58:19
【问题描述】:

这个问题不同于所有的:

堆栈溢出建议。


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

#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

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

【问题讨论】:

  • 移动构造以移动返回值,以及k 的复制构造。使用调试器单步执行代码应该可以确认这一点。请记住,复制省略已关闭。
  • operator* 返回一个const Int 似乎很奇怪。这是您获得副本而不是第二步的原因。
  • 当你返回没有复制省略的东西时,它必须被复制/移动到一个临时对象。您使用Int(i_ * rhs.i_) 创建一个临时文件,然后在您返回它时将其移至另一个临时文件。
  • @HolyBlackCat 谢谢。我现在明白了。
  • @super 是这样吗?这个实现是(编辑的)Effective C++ 3rd Edition 的摘录。您可以从GoogleBooks查看相关页面。我想知道是否有更好的实现。

标签: c++


【解决方案1】:

operator*Int(i_ * rhs.i_) 构造一个临时对象。它返回该对象,该对象在函数之外构造第二个临时对象。该临时对象被复制到k

【讨论】:

    【解决方案2】:

    当你写作时:

    Int k = i * j;
    

    你实际上是在做类似的事情:

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

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

    在这种情况下并不重要,因为即使你写了Int k(i*j),在调用k的复制构造函数之前,仍然需要一个临时的来保存operator*的返回值。

    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
    

    【讨论】:

    • tmp2 有两个析构函数,tmp1 没有评论...
    • @BenVoigt:明显的错字...谢谢,已修复。
    猜你喜欢
    • 2020-07-02
    • 1970-01-01
    • 2015-07-24
    • 1970-01-01
    • 2013-04-17
    • 2013-09-06
    • 1970-01-01
    • 1970-01-01
    • 2017-02-08
    相关资源
    最近更新 更多