为啥在已删除的默认 ctor 旁边定义一个空的副本 ctor 会使空列表的值初始化失败?
Posted
技术标签:
【中文标题】为啥在已删除的默认 ctor 旁边定义一个空的副本 ctor 会使空列表的值初始化失败?【英文标题】:Why does defining an empty copy ctor beside a deleted default ctor make a value initialization with empty list fail?为什么在已删除的默认 ctor 旁边定义一个空的副本 ctor 会使空列表的值初始化失败? 【发布时间】:2021-04-18 04:50:18 【问题描述】:简而言之,为什么下面的代码表现得像 cmets 中描述的那样?
struct A
A() = delete;
//A(const A&) // uncommenting this...
;
int main()
A a; // ... breaks this
//A(); // this fails in either case because of `A() = delete;` only
我应该查看标准的哪一部分(或至少 cppreference 上的一个页面)来理解这一点?
但是,写A(const A&) = default;
而不是//A(const A&)
不会破坏A a;
。那这个呢?我认为根本原因是一样的,但是真正懂C++的人说的比我认为要好。
【问题讨论】:
【参考方案1】:没有user-provided 复制构造函数,A
是一个聚合。是的,即使我们删除了默认构造函数。那是addressed in C++20。
因此,在 C++20 之前,A a;
是 aggregate initialization,因此不使用已删除的构造函数。当您取消注释复制构造函数时,A
将不再是聚合,从而将聚合初始化转换为 value initialization。所以a
的初始化会尝试调用被删除的构造函数。
为了从标准中判断初始化器的含义,通常从[dcl.init]/16 开始。通过项目符号,可以发现初始化程序的属性(当与相关类型的属性匹配时)将如何影响初始化发生的方式。
【讨论】:
我认为仅此一项就足以证明 C++ 只对专家友好的声誉。 我认为您的这条评论可以作为您回答的标题(我挑战您这样做!啊哈:P)。 @Enlico - 我不能说我完全喜欢这样移动球门柱。但无论哪种方式,这是完全相同的事情。它不再是用户提供的复制构造函数,因此不会干扰A
的聚合状态。
@Swift-FridayPie,实际上没有。我首先误读了您的评论(我读了没有用户定义的构造函数),我不明白,并想“如果我默认构造复制ctor会发生什么?”。
@Enlico 是同一枚硬币的两侧,两端都有尾巴。 A() = delete; A(const A&) = delete;
和 A() = delete; A(const A&) = default;
都可以在 C++17 中编译,而不能在 C++20 中编译。【参考方案2】:
聚合初始化不是值初始化
如果给定的类,比如S
,是一个聚合,那么
S s;
是聚合初始化,而不是值初始化,并且会绕过S
的任何构造函数,即使它们被删除或私有。聚合初始化的结果通常是聚合的 (public(1)) 数据成员的值初始化。
(1) 如下所示,具有任何私有数据成员的类绝不是聚合类,不符合任何 C++11 到 C++20 标准。
什么是聚合类的定义受以下因素支配:
C++11 中的[dcl.init.aggr]/1 (N3337) C++14 中的[dcl.init.aggr]/1 (N4140) C++17 中的[dcl.init.aggr]/1 (N4659) C++20 中的[dcl.init.aggr]/1 (N4861)与聚合的典型混淆是 C++11 到 C++17 的较弱要求是没有任何 用户提供的(2) 构造函数,这在 C++20 中变得更加严格,要求没有任何用户声明的构造函数。
(2) 用户提供函数的定义由 C++11 中的 [dcl.fct.def.default]/4 和 C++14 到 C++20 中的 [dcl.fct.def.default]/5 管理; user-provided 的定义在所有这些语言版本中基本相同。
C++11、C++14 和 C++17 之间的聚合定义还有其他一些不为人知的变化,但同样增加了 C++20 之前的聚合混乱;我们将通过几个示例来突出它们。
示例是聚合吗?
对于下面的每个示例;如果示例的类S
是一个聚合(针对特定语言版本),则以下格式正确:
S s;
上述所有示例中的聚合规则同样适用于任何类型的构造函数(假设它们在其他方面适用,例如允许显式默认或删除),而不仅仅是S()
。
struct S ;
C++11:是的
C++14:是的
C++17:是的
C++20:是的
struct S
int x;
;
C++11:是的
C++14:是的
C++17:是的
C++20:是的
struct S
int x42;
;
C++11:否(具有默认成员初始化程序)
C++14:是的
C++17:是的
C++20:是的
class S
int x;
;
C++11:否
C++14:否
C++17:否
C++20:否
有一个私有数据成员。
struct S
S() = default;
;
C++11:是的
C++14:是的
C++17:是的
C++20:否(具有用户声明的构造函数)
struct S
private:
S() = default;
;
C++11:是的
C++14:是的
C++17:是的
C++20:否(具有用户声明的构造函数)
struct S
S() = delete;
;
C++11:是的
C++14:是的
C++17:是的
C++20:否(具有用户声明的构造函数)
struct S
S();
;
S::S() = default;
C++11:否(具有用户提供的构造函数)
C++14:否 (...)
C++17:否 (...)
C++20:否(具有用户声明的构造函数)
显式默认定义不符合规定,视为用户提供;回顾 [dcl.fct.def.default]/4 (C++11) / [dcl.fct.def.default]/5 (C++14, C++17):
如果函数是用户声明的并且在其第一次声明时没有显式默认或删除,则该函数是用户提供的。
struct S
explicit S() = default;
;
C++11:是的
C++14:是的
C++17:否(有 explicit
构造函数)
C++20:否(具有用户声明的构造函数)
C++17 增加了聚合不能有 explicit
构造函数的要求。
struct S
int x42;
S() = delete;
;
C++11:否(具有默认成员初始化程序)
C++14:是的
C++17:是的
C++20:否(具有用户声明的构造函数)
C++14 删除了 C++11 对聚合没有默认成员初始值设定项的要求。
struct S
int x42;
explicit S() = delete;
;
C++11:否(具有默认成员初始化程序)
C++14:是的
C++17:否(有 explicit
构造函数)
C++20:否(具有用户声明的构造函数)
【讨论】:
以上是关于为啥在已删除的默认 ctor 旁边定义一个空的副本 ctor 会使空列表的值初始化失败?的主要内容,如果未能解决你的问题,请参考以下文章
在`dctor、copy ctor和copy assignment operator`中,为啥删除一个而让另一个隐式定义最有可能导致错误