查看标准中普通默认构造函数的定义:
如果默认构造函数不是用户提供的,并且如果:
,则默认构造函数是微不足道的
- 其类没有虚函数(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
不是POD
在C ++ 03中,C ++ 03允许goto
仅交叉定义POD
类型。
以字节方式的副本等价作为例子,标准说:
对于任何简单的可复制类型T,如果指向T的两个指针指向 不同的T对象obj1和obj2,其中obj1和obj2都不是 基类子对象,如果构成obj1的基础字节(1.7)是 复制到obj2,41 obj2随后应保持相同的值 OBJ1。
因此,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
不是POD
C ++ 03和C ++ 03仅允许POD
类型的字节副本等效。
答案 0 :(得分:7)
CWG issue 667通过N3225附近的C ++工作草案中包含的更改解决了这个问题。 N3225§12.1[class.ctor] / 5州:
如果默认构造函数既不是用户提供也不是删除,并且如果:
,则默认构造函数是微不足道的
- 其类没有虚函数(10.3),没有虚基类(10.1)和
- 其类的非静态数据成员没有大括号或等于初始化,
- 其类的所有直接基类都有简单的默认构造函数和
- 对于类类的所有非静态数据成员(或其数组),每个这样的类都有一个普通的默认构造函数。
否则,默认构造函数是非平凡。
在C ++ 11发布之前(显然)已经改变了。创建CWG DR 1135是为了解决a Finland national body comment on the C++11 candidate draft:
应该允许在第一个声明中显式默认非公共特殊成员函数。用户很可能希望默认受保护/私有构造函数和复制构造函数,而不必在类外部编写这样的默认值。
此问题的解决方案删除了12.1中的“未删除”文本以及描述琐碎析构函数,普通复制/移动构造函数和普通复制/移动赋值运算符的部分。我认为这种改变过于宽泛,而且可能无意使你的struct A
琐碎。事实上,从表面上来看,这个项目形成不良是荒谬的:
int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
a_x = a_y;
但是这个程序不是,因为A
可以轻易复制(Clang agrees,GCC does not):
int x = 42;
int y = 13;
A a_x{x};
A a_y{y};
std::memcpy(&a_x, &a_y, sizeof(a_x));
CWG issue 1496 "Triviality with deleted and missing default constructors"的存在似乎表明委员会已意识到问题(或至少是一个密切相关的问题):
根据12.1 [class.ctor]第5段,定义为已删除的默认构造函数是微不足道的。这意味着,根据9 [class]第6段,这样的类可能是微不足道的。但是,如果该类没有默认构造函数,因为它具有用户声明的构造函数,则该类并不简单。由于这两种情况都会阻止类的默认构造,因此不清楚为什么案例之间存在微不足道的差异。
虽然目前还没有解决方案。