简体   繁体   English

复制构造函数与 std::initializer_list 构造函数

[英]Copy constructor vs std::initializer_list constructor

Imagine that we have:想象一下,我们有:

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__); }
};

Which constructors should be called when S s2{S{}};S s2{S{}};时应该调用哪些构造函数? ? Is it ok that gcc and clang have different behavior? gcc 和 clang 有不同的行为可以吗?

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

gcc (trunk) output: gcc(主干)output:

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

clang (trunk) output: clang(主干)output:

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

GCC is correct here; GCC 在这里是正确的; list initialization does not allow for copy elision in C++17.列表初始化不允许 C++17 中的复制省略。

If you had done S s2(S{});如果你做了S s2(S{}); , this would be required to only call S 's default constructor, due to [dcl.init]/17.6.1 : ,这将只需要调用S的默认构造函数,因为[dcl.init]/17.6.1

If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.如果初始化表达式是纯右值且源类型的 cv 非限定版本与目标的 class 相同 class,则初始化表达式用于初始化目标 ZA8CFDE6331BD59EB2AC96F8911C4B6。 [ Example: T x = T(T(T())); [ 示例:T x = T(T(T())); calls the T default constructor to initialize x.调用 T 默认构造函数来初始化 x。 — end example ] —结束示例]

However, that only applies to copy initialization and direct initialization.但是,这只适用于复制初始化和直接初始化。

Doing S s2{S{}};S s2{S{}}; is list-initialization, which is its own entirely separate form of initialization with its own rules.是列表初始化,这是它自己完全独立的初始化形式,有自己的规则。 Since S is not an aggregate, [dcl.init.list]3.6 would take over, which says:由于S不是一个聚合, [dcl.init.list]3.6将接管,它说:

Otherwise, if T is a class type, constructors are considered.否则,如果 T 是 class 类型,则考虑构造函数。 The applicable constructors are enumerated and the best one is chosen through overload resolution ([over.match], [over.match.list]).枚举适用的构造函数,并通过重载决议([over.match]、[over.match.list])选择最佳构造函数。

Calling a constructor means invoking a specific function with a specific set of parameters.调用构造函数意味着使用一组特定的参数调用特定的 function。 And that means that the prvalue S{} must be used to initialize the parameter to the selected constructor.这意味着必须使用纯右值S{}来初始化所选构造函数的参数。 Which means you have to call the copy/move constructor.这意味着您必须调用复制/移动构造函数。

Regular, non-guaranteed, elision aren't allowed either.也不允许常规的、无保证的省略。 [class.copy.elision]/1 gives the 3 circumstances under which elision is allowed: return localVariableName , throw localVariableName , and catch(TypeName) if the catch matches what was thrown. [class.copy.elision]/1 给出了允许省略的 3 种情况: return localVariableNamethrow localVariableNamecatch(TypeName)如果 catch 与抛出的内容匹配。 This case is obviously none of these, so it doesn't qualify for regular elision.这种情况显然不是这些,所以它不符合常规省略的条件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM