【问题标题】:Why does copy elision not occur in this case?为什么在这种情况下不会发生复制省略?
【发布时间】:2018-03-08 11:15:00
【问题描述】:

考虑这段代码:

#include <iostream>

struct S
{
    S(std::string s) : s_{s} { std::cout << "S( string ) c-tor\n"; }
    S(S const&) { std::cout << "S( S const& ) c-tor\n"; }
    S(S&& s) { std::cout << "S&& c-tor\n"; s_ = std::move(s.s_); }
    S& operator=(S const&) { std::cout << "operator S( const& ) c-tor\n";  return *this;}
    S& operator=(S&& s) { std::cout << "operator (S&&)\n"; s_ = std::move(s.s_); return *this; }
    ~S() { std::cout << "~S() d-tor\n"; }

    std::string s_;
};

S foo() { return S{"blaaaaa"}; }

struct A
{
    A(S s) : s_{s} {}

    S s_;
};

struct B : public A
{
    B(S s) : A(s) {}
};

int main()
{
    B b(foo());
    return 0;
}

当我用g++ -std=c++1z -O3 test.cpp 编译它时,我得到以下输出:

S( string ) c-tor
S( S const& ) c-tor
S( S const& ) c-tor
~S() d-tor
~S() d-tor
~S() d-tor

我想知道为什么没有复制省略?我期待更多这样的东西:

S( string ) c-tor
~S() d-tor

当我使用 -fno-elide-constructors 编译时,输出相同

【问题讨论】:

  • 如果我计算正确,没有省略应该有四个复制构造函数调用,而不是你有的两个。我建议你在调试器中运行,在复制构造函数上设置一个断点,这样你就可以看到它是从哪里调用的。这可能会给你一些关于正在发生的事情的提示。

标签: c++ copy-elision


【解决方案1】:

正如预期的那样,foo 返回值确实会发生复制省略。

另外两个副本发生在 BA 构造函数中。请注意,在输出中它调用了两次S(S const&amp;),而人们希望看到至少一个S(S&amp;&amp;) 对应B(foo())。这是因为编译器已经消除了使用S(S&amp;&amp;) 创建的那些额外副本。如果您使用-fno-elide-constructors 编译,您可以看到这两个额外的副本:

S::S(std::string)
S::S(S&&)
S::~S()
S::S(S&&)
S::S(const S&)
S::S(const S&)
S::~S()
S::~S()
S::~S()
S::~S()

而没有-fno-elide-constructors 的输出是:

S::S(std::string)
S::S(const S&)
S::S(const S&)
S::~S()
S::~S()
S::~S()

copy initialization(用于函数参数的初始化):

首先,如果 T 是一个类类型,并且初始化程序是一个 prvalue 表达式,其 cv-unqualified 类型与 T 相同,则初始化程序表达式本身,而不是从它临时实现的,是用于初始化目标对象:参见copy elision

您可以通过引用接受来避免剩余的两个副本:

struct A
{
    A(S&& s) : s_{std::move(s)} {}
    S s_;
};

struct B : public A
{
    B(S&& s) : A(std::move(s)) {}
};

输出:

S( string ) c-tor <--- foo
S&& c-tor         <--- A::s_
~S() d-tor
~S() d-tor

【讨论】:

  • 会不会也是复制构造函数有改变std::cout状态的副作用?
  • @ArdaAytekin 嗯?
  • 对不起,没关系。我很困惑。实际上,在Bstd::moveing 到A 中按值接受S 也会减少副本的数量。正如您所说,这纯粹是因为构造函数签名。用户明确希望按值传递...对不起,再次造成混乱。
  • Maxim 你说复制省略发生在 foo 上,那么为什么我用 -fno-elide-constructors 编译它时得到相同的输出?此外,我仍然不明白为什么编译器不省略这两个副本?
  • 澄清一下:在 C++17 模式下编译时-fno-elide-constructors 没有任何区别,因为需要省略,并且不能关闭。在 C++14 模式下,省略是可选的,默认情况下完成,但可以使用 -fno-elide-constructors 关闭。删除由A(s)s_{s} 完成的额外两个副本是不允许的(在 C++14 或 C++17 中),因此编译器不会这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 2021-01-24
  • 1970-01-01
相关资源
最近更新 更多