保证复制省略的行为是否依赖于用户定义的复制构造函数?

18

GCC 8.0.1下,以下代码在有或没有自定义复制构造函数时表现不同:

#include <cassert>

struct S {
    int i;
    int *p;
    S() : i(0), p(&i) {}
    // S(const S &s) : i(s.i), p(&i) {}  // #1
    // S(const S &s) : i(s.i), p(s.p) {} // #2
    // S(const S &s) = delete;           // #3
};

S make_S() {return S{};}

int main()
{
    S s = make_S();
    assert(s.p == &s.i);
}

无论是使用任何一种已注释的用户自定义复制构造函数(即使是第二种,执行简单浅层复制的那种),断言都不会失败,这意味着保证复制省略按预期工作。

然而,如果没有任何用户自定义的复制构造函数,断言将失败,这意味着main函数中的对象s未被默认构造。为什么会发生这种情况?难道保证复制省略在此处不起作用吗?


1
保证复制省略和 RVO 不是同一回事,尽管它们有些相似。 - StoryTeller - Unslander Monica
1
在这个例子中,@StoryTeller RVO 符合保证复制省略的标准。它是从相同类类型的 prvalue 进行初始化。 - xskxzr
2
@juanchopanza 从这个表格来看,自GCC 7版本起,它完全支持保证复制省略。 - xskxzr
1
我可能错了,但我真的找不到你的示例中有什么问题会导致复制省略错误。我怀疑这是一个质量输出问题(对于Clang和GCC都是如此)。 - StoryTeller - Unslander Monica
1
仅供澄清,C++17中没有RVO,因为当返回一个prvalue时不会实例化任何临时对象。那么就没有需要省略的构造函数了。 - Daniel Langr
显示剩余2条评论
1个回答

17

引用自C++17工作草案§15.2临时对象第3段 (https://timsong-cpp.github.io/cppwp/class.temporary#3):

当将类类型X的对象传递给函数或从函数中返回时,如果X的每个复制构造函数、移动构造函数和析构函数都是平凡的或已删除的,并且X至少有一个未删除的复制或移动构造函数,则实现允许创建临时对象来保存函数参数或结果对象。... [ 注意:授予这种灵活性是为了允许将类类型的对象在寄存器中传递给函数或从函数中返回。— 结束说明]

在您的情况下,当我使复制和移动构造函数都默认时:

S(const S &) = default;
S(S &&) = default;

使用GCC和Clang时,断言失败了。请注意,隐式定义的构造函数是平凡的。


5
看来,扩大操作对象的大小对于原帖中的代码来说起到了关键作用,我猜测这是因为它不再适合存储在一个寄存器中。 - StoryTeller - Unslander Monica
1
@StoryTeller 没错,我也想到做同样的实验了。但你比我快 :) - Daniel Langr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接