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

时间:2014-04-02 12:54:27

标签: c++ c++11 language-lawyer

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

  

如果默认构造函数不是用户提供的,并且如果:

,则默认构造函数是微不足道的      
      
  • 其类没有虚函数(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类型的字节副本等效。

1 个答案:

答案 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 agreesGCC 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段,这样的类可能是微不足道的。但是,如果该类没有默认构造函数,因为它具有用户声明的构造函数,则该类并不简单。由于这两种情况都会阻止类的默认构造,因此不清楚为什么案例之间存在微不足道的差异。

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