删除的默认构造函数仍然可以是微不足道的?

goo*_*era 18 c++ language-lawyer c++11

查看标准中普通默认构造函数的定义:

如果默认构造函数不是用户提供的,则默认构造函数是微不足道的,如果:

  • 它的类没有虚函数(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, "");
}
Run Code Online (Sandbox Code Playgroud)

上面的代码在没有任何断言失败的情况下运行.该类型具有普通的默认构造函数,并且可以轻松复制,因此它是一个"trivial class".

不会让这种类型"trivial class"带来麻烦吗?例如,对象生命周期,字节副本等效,goto语句允许等.

编辑:以下goto allowance示例无效.感谢@ Casey的评论.添加了另一个逐字节副本等效的示例来替换这个.

goto声明津贴为例,标准说:

可以转换为块,但不能以初始化绕过声明的方式.从具有自动存储持续时间的变量不在范围内的点跳转到其在范围内的点的程序是不正确的,除非该变量具有标量类型,具有普通默认构造函数的类类型和普通的析构函数,这些类型之一的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;
}
Run Code Online (Sandbox Code Playgroud)

它是根据规则格式良好的,因为它A有一个简单的默认构造函数和一个简单的析构函数(断言传递OK).相反,代码在C++ 03中是不正确的(删除了C++ 11-only语法,即行A()=default;),因为A它不是PODC++ 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";
}
Run Code Online (Sandbox Code Playgroud)

它是根据规则定义的,因为它A是一个微不足道的类型(断言通过OK).结果表明,在不同时间引用不同的对象.相反,代码在C++ 03中未定义(仅删除C++ 11语法,即行A()=default;),因为A它不是PODC++ 03中的,而C++ 03允许按字节顺序复制POD只有类型的等价.

Cas*_*sey 7

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;
Run Code Online (Sandbox Code Playgroud)

但是这个程序不是,因为它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));
Run Code Online (Sandbox Code Playgroud)

CWG问题1496"存在删除和缺失默认构造函数的琐事"的存在似乎表明委员会已经意识到问题(或者至少是一个密切相关的问题):

根据12.1 [class.ctor]第5段,定义为已删除的默认构造函数是微不足道的.这意味着,根据9 [class]第6段,这样的类可能是微不足道的.但是,如果该类没有默认构造函数,因为它具有用户声明的构造函数,则该类并不简单.由于这两种情况都会阻止类的默认构造,因此不清楚为什么案例之间存在微不足道的差异.

虽然目前还没有解决方案.