删除的默认构造函数可能仍然微不足道
A deleted default constructor could still be trivial?
查看标准中普通默认构造函数的定义:
如果默认构造函数不是用户提供的,并且
如果:
- 它的类没有虚函数(10.3)和虚基类(10.1),并且
- 其类中没有非静态数据成员具有大括号或等于初始值设定项,并且
- 其类的所有直接基类都有简单的默认构造函数,并且
- 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个简单的默认值 构造 函数。
否则,默认构造函数是非平凡的。
似乎默认构造函数的琐碎性的定义并不排除deleted
默认构造函数的可能性:
struct A {
int& a; // the implicitly defaulted default constructor will be defined as deleted
};
struct B {
B()=delete; // explicitly deleted
};
int main() {
static_assert(is_trivial<A>::value, "");
static_assert(is_trivial<B>::value, "");
}
上面的代码运行没有任何断言失败。 该类型具有简单的默认构造函数,并且易于复制,因此它是一个"trivial class"
。
把这种类型做成"trivial class"
不会带来麻烦吗? 例如,对于对象生存期、按字节复制等效性、goto
语句余量等
编辑:以下goto允许示例无效。 感谢您@Casey的评论。 添加了另一个按字节复制等效的示例来替换此示例。
以goto
报表津贴为例,标准规定:
可以转移到一个块中,但不能以 通过初始化绕过声明。一个从87跳跃的程序 具有自动存储持续时间的变量不在 作用域到它在作用域中的点的格式不正确,除非 变量具有标量类型,具有简单默认值的类类型 构造函数和普通析构函数,一个符合 CV 标准的版本 这些类型或上述类型之一的数组,并声明 没有初始值设定项 (8.5)。
因此,对于以下代码:
class A {
int& a;
public:
A(int& aa): a{aa} {}
A()=default; // this is necessary otherwise no default constructor will be implicitly declared then the type will not be trivial
};
int i;
int main() {
static_assert(is_trivial<A>::value, "");
goto L;
A a{i};
L:
return 0;
}
根据规则,它的格式很好,因为它A
有一个普通的默认构造函数和一个普通析构函数(断言传递 OK)。 相反,代码在 C++03 中的格式不正确(删除了仅 C++11 的语法,即A()=default;
行),因为 A
不是 C++03 中的POD
,而 C++03 允许goto
仅交叉定义POD
类型。
以逐字节复制等价为例,标准说:
对于任何可平凡复制的类型 T,如果指向 T 的两个指针指向 不同的 T 对象 obj1 和 obj2,其中 obj1 和 obj2 都不是 基类子对象,如果构成 obj1 的基础字节 (1.7) 是 复制到 OBJ2,41 OBJ2 随后应保持与 对象1.
因此,对平凡可复制类型的memcpy()
是明确定义的:
class A {
int& a;
public:
A(int& aa): a{aa} {}
A()=default; // this is necessary otherwise no default constructor will be implicitly declared then the type will not be trivial
void* addr() {return &a;}
};
int i = 0;
int j = 0;
int main() {
static_assert(is_trivial<A>::value, "");
A a{i};
A b{j};
cout << a.addr() << " " << b.addr() << "n";
// a = b; // this will be ill-formed because the implicitly defaulted copy assignment is defined as deleted
memcpy(&a, &b, sizeof(A)); // this is well-defined because A is trivial
cout << a.addr() << " " << b.addr() << "n";
}
它是根据规则明确定义的,因为它A
是一个平凡的类型(断言通过 OK)。 结果表明,引用在不同时间引用不同的对象。 相反,代码在 C++03 中未定义(删除了仅 C++11 语法,即 A()=default;
行),因为A
不是 C++03 中的POD
,并且 C++03 仅允许POD
类型的按字节复制等效性。
CWG 第 667 期通过一项更改解决了这个问题,该更改已合并到 N3225 附近的C++工作草案中。N3225 § 12.1 [class.ctor]/5 规定:
如果默认构造函数既不是用户提供的,也不是删除
的,并且如果:
- 它的类没有虚函数(10.3)和虚基类(10.1),并且
- 其类中没有非静态数据成员具有大括号或等于初始值设定项,并且
- 其类的所有直接基类都有简单的默认构造函数,并且
- 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个简单的默认构造函数。
否则,默认构造函数是非平凡的。
这(显然)在 C++11 发布之前发生了变化。CWG DR 1135 的创建是为了解决芬兰国家机构对 C++11 候选草案的评论:
应该允许它在其第一个声明上显式默认非公共特殊成员函数。用户很可能希望默认受保护/私有构造函数和复制构造函数,而不必在类外部编写此类默认值。
此问题的解决方案从 12.1 中删除了"未删除"文本以及描述简单析构函数、简单复制/移动构造函数和简单复制/移动赋值运算符的部分。我认为这种变化的范围太广了,而且可能不是故意让你struct A
变得微不足道。事实上,从表面上看,这个程序格式不正确是荒谬的:
int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
a_x = a_y;
但这个程序不是,因为A
是微不足道的可复制的(Clang同意,GCC不同意):
int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
std::memcpy(&a_x, &a_y, sizeof(a_x));
CWG 第 1496 期"删除和丢失默认构造函数的琐碎性"的存在似乎表明委员会已经意识到了这个问题(或者至少是一个密切相关的问题):
根据 12.1 [class.ctor] 第 5 段,定义为已删除的默认构造函数是微不足道的。这意味着,根据9[类]第6段,这样的类可以是微不足道的。但是,如果该类没有默认构造函数,因为它具有用户声明的构造函数,则该类并非微不足道。由于这两种情况都阻止了类的默认构造,因此不清楚为什么这些情况之间存在琐碎性的差异。
虽然目前还没有解决方案。
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 如何使用默认参数等选择模板专业化
- 具有默认模板参数的多态类的模板推导失败
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 当函数模板参数是具有默认参数的类模板时,函数模板参数的推导如何执行
- 初始化具有非默认构造函数的std::数组项的更好方法
- 何时提供默认参数作为模板参数
- 是默认情况下分配给char数组常量的值
- 具有默认值的引用获取函数
- 具有默认模板类型的默认构造函数的类型推导
- 当给定默认值时,为什么此模板参数推导失败
- 修改 VS Code 中的默认C++代码段
- 声明默认的模板化函数
- 将const引用参数初始化为默认参数会导致悬空引用吗
- 如何使用非默认构造函数实例化模板化类
- 如何修复带有 clang 的参数'args'缺少默认参数的问题?
- 从具有默认值的部分指定模板类继承时发生SWIG错误,具有不带默认值的正向声明
- STD ::可选的微不足道默认构造函数
- 如果 T1 和 T2 有,std::p air<T1,T2>不应该有微不足道的默认构造函数吗?
- 删除的默认构造函数可能仍然微不足道