【发布时间】:2014-05-13 18:42:21
【问题描述】:
看标准中平凡默认构造函数的定义:
如果默认构造函数不是用户提供的并且如果:
- 它的类没有虚函数 (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 随后应保持与 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 在 C++03 中不是 POD,并且C++03 只允许 POD 类型的逐字节复制等价。
【问题讨论】:
-
这里clang和gcc是有区别的。在
gcc 4.8.2上,这些断言(和has_trivial_default_constructor)失败。在最近的 SVN 构建中的clang上,他们通过了。 -
构造函数是否必须存在才能是平凡的或非平凡的?
-
@pmr:我刚刚添加了一个
goto津贴的例子来表达我的疑问。 -
@user2357112:是的,这就是我的问题的重点,因为语言规则似乎与常识相冲突。
-
@pmr
clang version 3.5.0 (205153)在这里,在Ubuntu Saucy amd64下,代码的第一个sn-p 在编译时失败,来自svn 的clang++和g++ 4.8.1。 (第二个例子也是如此)。
标签: c++ c++11 language-lawyer