【问题标题】:Copy constructor vs std::initializer_list constructor复制构造函数与 std::initializer_list 构造函数
【发布时间】:2020-04-10 02:38:55
【问题描述】:

想象一下我们有:

struct S {
  struct S {
  S() { printf("%s\n", __PRETTY_FUNCTION__); }
  S(const S&) { printf("%s\n", __PRETTY_FUNCTION__); }
  S(S&&) { printf("%s\n", __PRETTY_FUNCTION__); }
  ~S() { printf("%s\n", __PRETTY_FUNCTION__); }

  S(std::initializer_list<S>) { printf("%s\n", __PRETTY_FUNCTION__); }
};

S s2{S{}}; 时应该调用哪些构造函数? gcc和clang有不同的行为可以吗?

示例:https://godbolt.org/z/qQxyp5

gcc(主干)输出:

S::S()
S::S(std::initializer_list<S>)
S::~S()
S::~S()

clang(主干)输出:

S::S()
S::~S()

【问题讨论】:

  • 这两种行为在技术上都是正确的,但 clang 的更优化。 gcc 在内部字符串上执行value-initialization,然后在外部字符串上执行list-initialization。 clang 看到s2 是由临时内部字符串和elides the temp away 构造的,直接构造s2。见When and why would I use -fno-elide-constructors?
  • godbolt 链接是一个很好的补充,但在问题中包含输出使其自包含链接是否有效。而且我不必单击链接并尝试找出该页面的相关部分。
  • @RemyLebeau:“clang 看到 s2 是由一个临时内部字符串构造的,并忽略了临时”我在标准中看不到任何允许这样的省略。 C++17 中的保证省略基于用于初始化对象的纯右值,但列表初始化违反了这一点,因为对于类类型,它会触发第 3.6 节,即调用构造函数。并且省略的通用规则不会列出从纯右值初始化列表的情况作为省略的可能情况。

标签: c++ g++ c++17 clang++


【解决方案1】:

GCC 在这里是正确的;列表初始化在 C++17 中不允许复制省略。

如果你已经完成了S s2(S{});,这将只需要调用S的默认构造函数,因为[dcl.init]/17.6.1

如果初始化表达式是纯右值并且源类型的 cv 非限定版本与目标类是同一类,则初始化表达式用于初始化目标对象。 [ 例子:T x = T(T(T()));调用 T 默认构造函数来初始化 x。 — 结束示例 ]

但是,这只适用于复制初始化和直接初始化。

S s2{S{}}; 是列表初始化,这是它自己完全独立的初始化形式,有自己的规则。由于S 不是聚合,[dcl.init.list]3.6 将接管,它表示:

否则,如果 T 是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议选择最佳的构造函数([over.match]、[over.match.list])。

调用构造函数意味着调用具有特定参数集的特定函数。这意味着必须使用纯右值S{} 来初始化所选构造函数的参数。这意味着您必须调用复制/移动构造函数。

也不允许常规的、无保证的省略。 [class.copy.elision]/1 给出了允许省略的 3 种情况:return localVariableNamethrow localVariableNamecatch(TypeName)(如果捕获与抛出的内容匹配)。这种情况显然不是这些,所以它不符合常规省略的条件。

【讨论】:

    最近更新 更多