【问题标题】:A deleted default constructor could still be trivial?删除的默认构造函数仍然是微不足道的吗?
【发布时间】: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


【解决方案1】:

CWG issue 667 通过在N3225 附近合并到 C++ 工作草案中的更改解决了这个确切的问题。 N3225 § 12.1 [class.ctor]/5 状态:

如果默认构造函数既不是用户提供也不是被删除,并且如果:

  • 它的类没有虚函数 (10.3) 和虚基类 (10.1),并且
  • 其类的非静态数据成员没有brace-or-equal-initializer,并且
  • 其类的所有直接基类都有微不足道的默认构造函数,并且
  • 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个普通的默认构造函数。

否则,默认构造函数是非平凡的

这在 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 段,这样的类可以是微不足道的。但是,如果该类没有默认构造函数,因为它有一个用户声明的构造函数,那么该类就不是微不足道的。由于这两种情况都阻止了类的默认构造,因此尚不清楚为什么这两种情况之间存在琐碎性差异。

虽然目前还没有解决办法。

【讨论】:

  • 非常感谢您提供的所有信息。这似乎是标准中的一个缺陷。
猜你喜欢
  • 2021-09-25
  • 2018-08-29
  • 2023-03-14
  • 2013-08-19
  • 2019-05-07
  • 1970-01-01
  • 1970-01-01
  • 2012-06-30
相关资源
最近更新 更多