C++ 什么是骨料和豆荚,它们如何/为什么特别?

C++ 什么是骨料和豆荚,它们如何/为什么特别?,c++,c++11,aggregate,c++17,standard-layout,c++20,C++,C++11,Aggregate,C++17,Standard Layout,C++20,这是关于骨料和豆荚的,包括以下材料: 什么是聚合? 什么是POD?普通的旧数据? 它们有什么关系? 它们是如何和为什么特别的? C++11有什么变化? 如何阅读: 这篇文章相当长。如果您想了解聚合和POD,请花时间阅读普通的旧数据。如果您只对聚合感兴趣,请只阅读第一部分。如果你只对PODs感兴趣,那么你必须首先阅读聚合的定义、含义和示例,然后你可以跳转到PODs,但我仍然建议你完整阅读第一部分。集合的概念对于定义豆荚至关重要。如果你发现任何小错误,包括语法、文体、格式、语法等,请留下评论,我会编

这是关于骨料和豆荚的,包括以下材料:

什么是聚合? 什么是POD?普通的旧数据? 它们有什么关系? 它们是如何和为什么特别的? C++11有什么变化? 如何阅读: 这篇文章相当长。如果您想了解聚合和POD,请花时间阅读普通的旧数据。如果您只对聚合感兴趣,请只阅读第一部分。如果你只对PODs感兴趣,那么你必须首先阅读聚合的定义、含义和示例,然后你可以跳转到PODs,但我仍然建议你完整阅读第一部分。集合的概念对于定义豆荚至关重要。如果你发现任何小错误,包括语法、文体、格式、语法等,请留下评论,我会编辑

这个答案适用于C++03。对于其他C++标准,请参阅:

什么是集合,为什么它们是特殊的

从C++标准C++ 03到8.5.1的形式定义1:< /P> 聚合是没有声明用户的数组或类子句9 构造函数12.1,无私有或受保护的非静态数据成员第11条, 第10条没有基类,第10.3条没有虚函数

好吧,让我们分析一下这个定义。首先,任何数组都是一个聚合。类也可以是聚合,如果…等等!没有关于结构或联合的说明,它们不能是聚合吗?是的,他们可以。在C++中,术语类指的是所有类、结构和联合。因此,类、结构或联合当且仅当满足上述定义中的条件时才是聚合。这些标准意味着什么

这并不意味着聚合类不能有构造函数,事实上它可以有默认构造函数和/或副本构造函数,只要它们是由编译器隐式声明的,而不是由用户显式声明的

没有私有或受保护的非静态数据成员。您可以有任意多个私有和受保护的成员函数,但不能有构造函数,也可以有任意多个私有或受保护的静态数据成员和成员函数,并且不违反聚合类的规则

聚合类可以具有用户声明/用户定义的复制赋值运算符和/或析构函数

数组是聚合的,即使它是非聚合类类型的数组

现在让我们看一些例子:

class NotAggregate1
{
  virtual void f() {} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static 
};

class NotAggregate3
{
public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
private:
  void f() {} // ok, just a private function
};
你明白了。现在让我们看看聚合是如何特殊的。与非聚合类不同,它们可以用大括号{}初始化。这种初始化语法通常用于数组,我们刚刚了解到这些是聚合。那么,让我们从它们开始

键入数组_name[n]={a1,a2,…,am}

ifm==n 数组的第i个元素用ai初始化 否则,如果mn 编译器将发出一个错误 否则,当n根本不像int a[]={1,2,3}那样被指定时,就是这种情况; 假设数组n的大小等于m,因此int a[]={1,2,3};等价于int a[3]={1,2,3}

当标量类型为bool、int、char、double、pointers等的对象初始化为值时,表示该类型的对象初始化为0,bool为false,double为0.0,等等。。当初始化具有用户声明的默认构造函数的类类型的对象时,将调用其默认构造函数。如果默认构造函数是隐式定义的,那么所有非静态成员都是递归初始化的。这个定义是不精确的,有点不正确,但它应该给你一个基本的想法。无法初始化引用的值。例如,如果非聚合类没有适当的默认构造函数,则该类的值初始化可能会失败

阵列初始化示例:

class A
{
public:
  A(int) {} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}
现在让我们看看如何用大括号初始化聚合类。差不多一样。我们将不使用数组元素,而是按照非静态数据成员在类定义中的出现顺序来初始化它们。根据定义,它们都是公共的。如果初始值设定项少于成员,则其余的值将被初始化。如果无法对未显式初始化的成员之一进行值初始化,则会出现编译时错误。如果初始值设定项过多,我们也会得到编译时错误

struct X
{
  int i1;
  int i2;
};
struct Y
{
  char c;
  X x;
  int i[2];
  float f; 
protected:
  static double d;
private:
  void g(){}      
}; 

Y y = {'a', {10, 20}, {20, 30}};
在上述示例中,y.c用“a”初始化,y.x.i1用10初始化,y.x.i2用20初始化,y.i[0]用20初始化,y.i[1]用30初始化,y.f用值初始化,即,用0.0初始化。受保护的静态成员d根本没有初始化,因为它是静态的

聚合联合的不同之处在于,可以仅使用大括号初始化其第一个成员。我认为,如果你在C++中足够先进,甚至考虑使用工会,他们的使用可能是非常危险的。 必须仔细考虑,您可以自己在标准中查找工会规则:

现在我们知道了聚合的特殊之处,让我们试着理解类的限制;这就是为什么他们会在那里。我们应该理解,使用大括号进行成员初始化意味着类只不过是其成员的总和。如果存在用户定义的构造函数,这意味着用户需要做一些额外的工作来初始化成员,因此大括号初始化将不正确。如果存在虚拟函数,这意味着该类的对象在大多数实现中都有一个指向该类的所谓vtable的指针,该指针是在构造函数中设置的,因此大括号初始化是不够的。您可以通过与练习类似的方式来了解其余的限制:

关于总量的问题已经足够了。现在我们可以定义一组更严格的类型,也就是POD

豆荚是什么?它们为什么特别 < C++标准C++ 03的形式定义9<4:

POD结构是一个聚合类 没有非静态数据成员的 输入非POD结构、非POD并集或 此类类型或引用的数组,以及 没有用户定义的副本分配 运算符,并且没有用户定义的 析构函数。类似地,POD联盟也是如此 一个没有工会组织的总工会 类型的非静态数据成员 非POD结构、非POD并集或 此类类型或引用的数组,以及 没有用户定义的副本分配 运算符,并且没有用户定义的 析构函数。一个豆荚类就是一个类 这是一个POD结构或一个 豆荚联盟

哇,这个更难解析,不是吗让我们以上述相同的理由将工会排除在外,并以更清晰的方式重新表述:

聚合类称为POD,如果 它没有用户定义的副本分配 运算符和析构函数,而不是 它的非静态成员是非POD 类、非POD数组或 参考资料

这个定义意味着什么?我有没有提到POD代表普通的旧数据

所有的POD类都是聚合的,或者,换句话说,如果一个类不是聚合的,那么它肯定不是POD 类,就像结构一样,可以是POD,即使标准术语在这两种情况下都是POD-struct 与聚合的情况一样,类具有哪些静态成员并不重要 示例:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1() {} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};
后藤声明。您可能知道,编译器应该发出错误,通过goto从某个变量尚未在作用域中的点跳转到该变量已在作用域中的点,这是非法的。仅当变量为非POD类型时,此限制才适用。在下面的例子中,f是病态的,而g是良态的。请注意,微软的编译器对这条规则过于宽松,在这两种情况下都会发出警告

int f()
{
  struct NonPOD {NonPOD() {}};
  goto label;
  NonPOD x;
label:
  return 0;
}

int g()
{
  struct POD {int i; char c;};
  goto label;
  POD x;
label:
  return 0;
}
可以保证POD对象的开头不会有填充。换句话说,如果POD类a的第一个成员的类型为T,则可以安全地将_cast从a*重新解释为T*,并获取指向第一个成员的指针,反之亦然

清单上还有很多

结论 理解POD到底是什么很重要,因为正如您所看到的,许多语言功能对它们的行为是不同的。

C++11有什么变化? 聚集体 聚合的标准定义稍有变化,但仍然基本相同:

聚合是一个数组或类子句9,没有用户提供的构造函数12.1, 非静态数据成员9.2没有大括号或相等的初始值设定项,没有私有或受保护的 第11条为非静态数据成员,第10条为无基类,第10.3条为无虚拟函数

好的,什么改变了

以前,聚合不能有用户声明的构造函数,但现在它不能有用户提供的构造函数。有区别吗?是的,有,因为现在您可以声明构造函数并默认它们:

这仍然是一个聚合,因为在第一次声明中默认的构造函数或任何特殊成员函数不是用户提供的

现在,对于非静态数据成员,聚合不能有任何大括号或相等的初始值设定项。这是什么意思?这只是因为有了这个新标准,我们可以直接在类中初始化成员,如下所示:

struct NotAggregate {
    int x = 5; // valid in C++11
    std::vector<int> s{1,2,3}; // also valid
};
标准布局 标准布局是第二个属性。该标准提到,它们对于与其他语言通信非常有用,这是因为标准布局类具有与等效C结构或联合相同的内存布局

这是成员和所有基类必须递归保存的另一个属性。和往常一样,不允许使用虚拟函数或虚拟基类。这将使布局与C不兼容

这里一个宽松的规则是,标准布局类必须具有具有相同访问控制的所有非静态数据成员。以前这些都必须是公共的,但现在你可以将它们设置为私有的或受保护的,只要 所有人都是私人的或都受到保护的

使用继承时,整个继承树中只有一个类可以具有非静态数据成员,并且第一个非静态数据成员不能是基类类型。这可能会破坏别名规则,否则,它不是标准布局类

以下是标准文本中的定义:

标准布局类是指:

-没有非标准布局类或此类类型的数组类型的非静态数据成员 或参考

-没有虚拟函数10.3和虚拟基类10.1

-对所有非静态数据成员具有相同的访问控制第11条

-没有非标准布局基类

-在最派生的类中没有非静态数据成员,并且最多有一个基类具有 非静态数据成员,或没有包含非静态数据成员的基类,以及

-没有与第一个非静态数据成员类型相同的基类

标准布局结构是使用类键struct或 这个班是重点班

标准布局联合是使用类键联合定义的标准布局类

[注:标准布局类对于与其他编程语言编写的代码进行通信非常有用。它们的布局在9.2.-结束说明中有规定]

让我们看几个例子

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's ok
    int x;
    int y;
};

struct StandardLayout4 : StandardLayout1 {
    int x;
    int y;

    void f(); // perfectly fine to have non-virtual functions
};

struct StandardLayout5 : StandardLayout1 {
    int x;
    StandardLayout1 y; // can have members of base type if they're not the first
};

struct StandardLayout6 : StandardLayout1, StandardLayout5 {
    // can use multiple inheritance as long only
    // one class in the hierarchy has non-static data members
};

struct StandardLayout7 {
    int x;
    int y;
    StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};

struct StandardLayout8 {
public:
    StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
    int x;
};

struct StandardLayout9 {
    int x;
    static NonStandardLayout1 y; // no restrictions on static members
};

struct NonStandardLayout1 {
    virtual f(); // cannot have virtual functions
};

struct NonStandardLayout2 {
    NonStandardLayout1 X; // has non-standard-layout member
};

struct NonStandardLayout3 : StandardLayout1 {
    StandardLayout1 x; // first member cannot be of the same type as base
};

struct NonStandardLayout4 : StandardLayout3 {
    int z; // more than one class has non-static data members
};

struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class
结论 有了这些新规则,现在可以有更多类型的pod了。即使一个类型不是POD,如果它只是一个普通的或标准的布局,我们也可以单独利用一些POD属性

标准库具有测试标头中这些属性的特性:


C++11中的POD基本上分为两个不同的轴:平凡性和布局。琐碎性是关于对象的概念值与其存储中的数据位之间的关系。布局是关于。。。对象子对象的布局。只有类类型具有布局,而所有类型都具有平凡关系

这就是平凡轴的意义所在:

非平凡的可复制性:这种类型的对象的值可能不仅仅是直接存储在对象中的二进制数据

例如,unique_ptr存储一个T*;这是对象中二进制数据的总和。但这并不是一个独特的ptr价值的总和。unique_ptr存储null ptr或指向其生存期由unique_ptr实例管理的对象的指针。这种管理是独一无二的ptr价值的一部分。并且该值不是对象的二进制数据的一部分;它是由该对象的各种成员函数创建的

例如,将nullptr分配给唯一的_ptr不仅仅是更改对象中存储的位。此类分配必须销毁由唯一\u ptr管理的任何对象。在不通过其成员函数的情况下操纵唯一_ptr的内部存储将破坏此机制,在不破坏其当前管理的对象的情况下更改其内部T*将违反该对象所拥有的概念值

可复制性差:这些对象的值完全是且仅是其二进制存储的内容。这就是允许复制二进制存储相当于复制对象本身的原因

定义平凡可复制性的特定规则平凡析构函数、平凡/删除的复制/移动构造函数/赋值是类型仅为二进制值所必需的。对象的析构函数可以参与定义对象的值,如unique_ptr。如果该析构函数是平凡的,那么它不参与定义对象的值

专门的复制/移动操作也可以参与对象的值。unique_ptr的move构造函数通过将move操作的源置空来修改它。这就是确保unique_ptr的值唯一的原因。琐碎的复制/移动操作意味着这样的对象值骗局不会被播放,因此对象的值只能是它存储的二进制数据

平凡:这个对象被认为对它存储的任何位都有一个函数值。普通可复制定义了对象的数据存储的含义,即仅为该数据。但这些类型在某种程度上仍然可以控制数据如何到达那里。此类类型可以具有默认成员初始值设定项和/或默认构造函数,以确保特定成员始终具有特定值。因此,对象的概念值可以限制为它可以存储的二进制数据的子集

对具有普通默认构造函数的类型执行默认初始化将使该对象具有完全未初始化的值。因此,具有普通默认构造函数的类型在逻辑上对其数据存储中的任何二进制数据都是有效的 布局轴非常简单。编译器在决定类的子对象如何存储在类的存储中时有很大的回旋余地。然而,在某些情况下,这种回旋余地是不必要的,并且具有更严格的排序保证是有用的

这些类型是标准布局类型。而C++标准甚至没有真正说明什么是具体的布局。它基本上说明了标准布局类型的三个方面:

第一个子对象与对象本身位于同一地址

可以使用offsetof获取从外部对象到其成员子对象之一的字节偏移量

如果活动成员至少部分地使用与正在访问的非活动成员相同的布局,则联合可以通过联合的非活动成员访问子对象

<>编译器通常允许标准布局对象映射到具有相同成员的结构类型,但在C++标准中没有声明;这正是编译器想要做的

在这一点上,POD基本上是一个无用的术语。它只是简单的可复制性的交集,其值仅为二进制数据和标准布局,其子对象的顺序定义得更明确。人们可以从这些事情中推断,类型是类似C的,并且可以映射到类似的C对象。但该标准没有关于这一点的声明


请详细说明以下规则:

我会尝试:

标准布局类必须具有具有相同访问控制的所有非静态数据成员

这很简单:所有非静态数据成员都必须是公共的、私有的或受保护的。你不能有公共的和私人的

他们的理由是为了区分标准布局和非标准布局。也就是说,让编译器自由选择如何将内容放入内存。这不仅仅是关于vtable指针

当他们在98标准化C++时,他们必须基本上预测人们如何实现它。尽管他们有很多不同的C++语言的执行经验,但他们对事情并不确定。因此,他们决定谨慎行事:给编译器尽可能多的自由

这就是为什么C++98中POD的定义如此严格的原因。它给C++编译器在大多数类的成员布局上提供了很大的自由度。基本上,POD类型是为了特殊情况而设计的,这是您专门为某个原因编写的

当开发C++11时,他们对编译器有了更多的经验。他们意识到。。。C++编译器编写者真的很懒。他们有所有这些自由,但他们什么也没做

标准布局的规则或多或少地规范了常见的实践:大多数编译器实际上不需要做太多的更改(如果有任何更改的话)就可以实现它们,除了相应的类型特征之外

现在,当涉及公共/私人时,情况就不同了。重新排序哪些成员是公共成员还是私有成员的自由实际上对编译器很重要,特别是在调试构建时。由于标准布局的要点是与其他语言兼容,所以在调试和发布时,布局不能不同

事实上,这并不会真正伤害用户。如果您正在创建一个封装类,那么您的所有数据成员都很可能是私有的。通常不会在完全封装的类型上公开公共数据成员。因此,这只会是少数真正想这样做的用户的一个问题,他们想要这样的划分

所以这不是什么大损失

b整个继承树中只有一个类可以有非静态数据成员

之所以这样,是因为他们再次标准化了标准布局:常规做法

当一个继承树中有两个成员实际存储东西时,没有常见的做法。有些人将基类放在派生类之前,另一些人则以另一种方式进行。如果成员来自两个基类,您会以哪种方式排序?等等编译器在这些问题上分歧很大

此外,由于零/一/无限规则,一旦你说你可以有两个成员的类,你可以说你想要多少。这需要添加许多布局规则来处理此问题。您必须说明多重继承是如何工作的,哪些类将其数据放在其他类之前,等等。这是很多规则,但实际收益很少

如果没有虚拟函数和默认构造函数标准布局,则无法制作所有内容

并且第一个非静态数据成员不能是基类类型,这可能会破坏别名规则

我真的不能和这个人说话。我对C++的别名规则没有足够的了解,无法真正理解它。但这与基本成员将与用户共享同一地址这一事实有关 基类本身。即:

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;
这可能违反了C++的别名规则。在某种程度上

但是,请考虑一下:这样做的能力到底有多有用?因为只有一个类可以有非静态数据成员,所以派生类必须是该类,因为它有一个基作为成员。所以Base必须是空的数据。如果基类为空,以及基类。。。为什么有一个数据成员呢

因为Base是空的,所以它没有状态。因此,任何非静态成员函数都将根据其参数执行操作,而不是根据其this指针执行操作

再次强调:没有大的损失。

C++14发生了什么变化 我们可以参考参考

聚集体 第8.5.1节“骨料”对此进行了说明,该节给出了以下定义:

聚合是没有提供用户的数组或类子句9 构造函数12.1,没有私有或受保护的非静态数据成员 第11条,无基类第10条,无虚函数 10.3

现在唯一的变化是添加类成员初始值设定项不会使类成为非聚合类。下面的例子来自:

在C++11中不是聚合,但在C++14中是聚合。本变更包含在中,其摘要如下:

比亚恩·斯特劳斯特鲁普和理查德·史密斯提出了一个关于总量的问题 初始化和成员初始值设定项不一起工作。这 该文件建议采用史密斯提议的措辞来解决这个问题 这消除了聚合不能具有的限制 成员初始值设定者

豆荚保持不变 第9节“类”介绍了PODplain旧数据结构的定义,其中说明:

POD结构110是一个非并集类,它既是一个普通类又是一个非并集类 标准布局类,并且没有类型为的非静态数据成员 此类类型的非POD结构、非POD并集或数组。同样,一个 POD联合是一个联合,它既是一个平凡的类又是一个简单的类 标准布局类,并且没有类型为的非静态数据成员 此类类型的非POD结构、非POD并集或数组。豆荚类是 作为POD结构或POD联合的类

这与C++11的措辞相同

C++14的标准布局更改 正如评论中所指出的,pod依赖于标准布局的定义,对于C++14来说确实发生了变化,但这是通过事后应用于C++14的缺陷报告实现的

共有三个DRs:

从C++14之前的版本开始:

标准布局类是指:

7.1没有此类类型或参考的非标准布局类或数组类型的非静态数据成员, 7.2没有虚拟函数[class.virtual]和虚拟基类[class.mi], 7.3对所有非静态数据成员具有相同的访问控制条款[class.access], 7.4没有非标准布局基类, 7.5在最派生的类中没有非静态数据成员,并且最多有一个基类具有非静态数据成员,或者 没有包含非静态数据成员的基类,以及 7.6没有与第一个非静态数据成员相同类型的基类。109 致:

类S是标准布局类,如果它:

3.1没有此类类型或参考的非标准布局类或数组类型的非静态数据成员, 3.2没有虚拟函数和虚拟基类, 3.3对所有非静态数据成员具有相同的访问控制, 3.4没有非标准布局基类, 3.5最多有一个给定类型的基类子对象, 3.6在同一类中首先声明类及其基类中的所有非静态数据成员和位字段,以及 3.7没有类型集MS的元素作为基类,其中对于任何类型X,MX的定义如下。104 [ 注意:MX是所有非基类子对象的类型集,这些子对象在X中的偏移量可能为零。 — 尾注  ] 3.7.1如果X是非联合类类型,没有可能继承的非静态数据成员,则集合MX为空。 3.7.2如果X是非联合类类型,且X0类型的非静态数据成员大小为零或是第一个 X的非静态数据成员,其中所述成员可以是匿名成员 联合,集合MX由X0和MX0的元素组成。 3.7.3如果X是联合类型,则集合MX是所有MUi和包含所有Ui的集合的联合,其中每个Ui是 X的第i个非静态数据成员。 3.7.4如果X是元素类型为Xe的数组类型,则集合MX由Xe和MXe的元素组成。 3.7.5如果X是非类、非数组类型,则设置MX为空。 C++17中的变化 下载C++17国际标准最终草案

聚集体

C++17扩展并增强了聚合和聚合初始化。标准库现在还包括一个std::is_聚合类型特征类。以下是第11.6.1.1节和第11.6.1.2节内部参考中省略的正式定义:

聚合是具有 -没有用户提供的、显式的或继承的构造函数, -无私有或受保护的非静态 数据成员, -没有虚拟函数,并且 -没有虚拟、私有或受保护的基类。 [注意:聚合初始化不允许访问受保护和私有基类的成员或构造函数。-结束注意] 聚合的元素包括: -对于数组,按递增下标顺序排列的数组元素,或 -对于类,按声明顺序排列的直接基类,然后按声明顺序排列的是不是匿名联合成员的直接非静态数据成员

什么改变了

聚合现在可以有公共的非虚拟基类。此外,基类不是聚合的要求。如果它们不是聚合,则将对它们进行列表初始化。 不允许显式默认构造函数 不允许继承构造函数 平凡类

平凡类的定义在C++17中进行了修改,以解决C++14中未解决的几个缺陷。这些变化是技术性的。以下是12.0.6内部参考中省略的新定义:

一个普通的可复制类是一个类: -其中每个复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都被删除或不重要, -至少有一个未删除的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符,以及 -它有一个简单的、未删除的析构函数。 一个平凡类是一个平凡可复制的类,它有一个或多个默认构造函数,所有这些构造函数要么平凡要么被删除,至少有一个未被删除。[注:特别是一个可复制的 或者普通类没有虚拟函数或虚拟基类。-结束说明]

变化:

在C++14中,对于一个平凡的类,该类不能有任何非平凡的复制/移动构造函数/赋值运算符。但是,隐式声明为默认构造函数/运算符的类可能非常重要,但定义为已删除,因为该类包含无法复制/移动的类类型的子对象。这种定义为已删除构造函数/运算符的非平凡类的存在将导致整个类成为非平凡类。析构函数也存在类似的问题。C++17澄清了此类构造函数/运算符的存在不会导致类具有非平凡可复制性,因此不具有平凡可复制性,并且平凡可复制类必须具有平凡的、未删除的析构函数, C++14允许一个普通的可复制类(因此是一个普通类)将每个复制/移动构造函数/赋值运算符声明为已删除。但是,如果类也是标准布局,则可以使用std::memcpy合法地复制/移动它。这是一个语义矛盾,因为通过将所有构造函数/赋值运算符定义为deleted,类的创建者显然希望该类不能被复制/移动,但该类仍然满足可复制类的定义。因此,在C++17中,我们有一个新的子句,说明可复制的平凡类必须至少有一个不可删除的平凡类,尽管不一定是可公开访问的复制/移动构造函数/赋值运算符。看见 第三个技术变化涉及默认构造函数的类似问题。在C++14下,一个类可以有一个简单的默认构造函数,该构造函数被隐式定义为deleted,但仍然是一个简单的类。新定义澄清了一个平凡类必须至少有一个平凡的、未删除的默认构造函数。看见 标准布局类

标准布局的定义也进行了修改,以解决缺陷报告。这些变化也是技术性的。以下是标准12.0.7中的文本。如前所述,省略了内部参考:

类S是标准布局类,如果它: -没有此类类型或引用的非标准布局类或数组类型的非静态数据成员, -没有虚拟函数和虚拟基类, -对所有非静态数据成员具有相同的访问控制, -没有非标准布局基类, -最多有一个给定类型的基类子对象, -类及其基类中的所有非静态数据成员和位字段首先在同一个类中声明,并且 -没有以下定义为基类的类型集MS的元素。108 MX的定义如下: -如果X是一个非联合类类型,没有可能继承的非静态数据成员,则集合MX为空。 -如果X是非联合类类型,其第一个非静态数据成员的类型为X0,其中所述成员可能是匿名联合,则集合MX由X0和MX0的元素组成。 -如果X是联合类型,则集合MX是所有MUi和包含所有Ui的集合的联合,其中每个Ui是X的第i个非静态数据成员的类型。 -如果X是元素类型为Xe的数组类型,则集合MX由Xe和MXe的元素组成。 -如果X是非类、非数组类型,则集合MX为空。 [注:MX是所有非基类子对象的类型集,这些子对象在标准布局类中保证在X中处于零偏移。-结束注] [示例: -[结束示例] 108这可确保具有相同类类型且属于同一最派生对象的两个子对象不会分配在同一地址

变化:

阐明了派生树中只有一个类具有非静态数据成员的要求是指首先声明此类数据成员的类,而不是可以继承此类数据成员的类,并将此要求扩展到非静态位字段。还阐明了标准布局类最多有一个给定类型的基类子对象。看见 标准布局的定义从未允许任何基类的类型与第一个非静态数据成员的类型相同。这是为了避免偏移量为零的数据成员与任何基类具有相同类型的情况。C++17标准对所有非基类子对象的类型集提供了更严格的递归定义,这些子对象在标准布局类中保证为零偏移,从而禁止此类类型成为任何基类的类型。看见 注:C++标准委员会希望上述基于缺陷报告的更改适用于C++ 14,尽管新语言不在已发布的C++ 14标准中。它在C++17标准中。

中有什么变化 在这个问题的明确主题的其余部分之后,集合的含义和使用继续随着每个标准的变化而变化。有几个关键的变化即将出现

具有用户声明的构造函数的类型 在C++17中,此类型仍然是聚合:

struct X {
    X() = delete;
};
因此,X{}仍然可以编译,因为这是聚合初始化,而不是构造函数调用。另见:

在C++20中,限制将从要求更改为:

没有用户提供、显式或继承的构造函数

没有用户声明或继承的构造函数

这一点已被采纳到《公约》中。此处的X和链接问题中的C都不会在C++20中聚合

以下示例中,这也会产生溜溜球效果:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
在C++11/14中,由于基类的原因,B不是一个聚合,因此B{}在可以访问的点执行值初始化,调用B::B,调用A::A。这是很有条理的

在C++17中,B成为聚合,因为允许基类,这使得B{}聚合初始化。这需要复制列表从{}初始化一个A,但是从B的上下文外部初始化,在B的上下文中它是不可访问的。在C++17中,这是格式错误的auto x=B;不过也可以

现在在C++20中,由于上述规则的更改,B再次不再是聚合,这不是因为基类,而是因为用户声明的默认构造函数——即使它是默认的。我们回到B的构造函数,这段代码的格式很好

从带括号的值列表初始化聚合 出现的一个常见问题是希望将emplace样式的构造函数用于聚合:

struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
用户必须为所有聚合模板编写自己的扣减指南:

template <typename T> Point(T, T) -> Point<T>;

但从某种意义上说,这是显而易见的事情,基本上只是样板,语言会为您做到这一点。此示例将在C++20中编译,无需用户提供的演绎指南

回答得好。注释:如果默认构造函数是隐式定义的,那么所有非静态成员都是递归值初始化的。而非聚合类的值初始化可能会失败,例如,如果该类没有适当的默认构造函数。不正确:具有隐式声明的默认构造函数的类的值初始化不需要隐式定义的默认构造函数。因此,给定insert private:视情况而定:struct A{int const A;};那么A是格式正确的,即使A的默认构造函数定义是格式错误的。@Kev:如果你能将相同的信息打包成一个较短的答案,我们都会很乐意投票@Armen也注意到你可以对同一个问题做多个答案。每个答案都可以包含问题的部分解决方案。去他妈的那个接受马克的东西,在我看来:答案很好。我仍然会重温这篇文章好几次。顺便提一下,关于Visual Studio的警告。pod的goto语句对MSVC编译器来说是无知的,正如您所提到的。但对于switch/case语句,它会生成编译错误。基于它的概念,我做了一些测试pod检查器:在要点中,从非pod类类型的对象的生存期开始,在构造函数完成时开始,在析构函数完成时结束。最后一部分应该说析构函数何时启动。你能详细说明以下规则吗:标准布局cla

SSE必须具有具有相同访问控制的所有非静态数据成员;b整个继承树中只有一个类可以有非静态数据成员,并且第一个非静态数据成员不能是基类类型,这可能会破坏别名规则。特别是原因是什么?对于后面的规则,你能提供一个打破别名的例子吗?@AndyT:见我的答案。我试图尽我所知回答问题。可能希望在C++14中更新此内容,因为C++14删除了对聚合的无括号或相等初始值设定项要求。@T.C.谢谢提醒。我将很快查找这些更改并更新它。关于混淆:有一个C++布局规则,如果C类有一个空的基X,C的第一个数据成员是X类型,那么第一个成员不能与基X相同的偏移量;如果需要避免的话,它前面会有一个虚拟的填充字节。在同一地址有两个X或子类实例可能会破坏需要通过其地址区分不同实例的东西空实例没有其他东西可以区分它。。。。在任何情况下,填充字节的需要都会破坏“布局兼容”。感谢您的解释,这非常有帮助。可能尽管static_cast&d和&d.b是相同的Base*类型,但它们指向不同的对象,因此违反了别名规则。请纠正我。为什么如果只有一个类可以有非静态数据成员,那么派生类必须是该类?@AndyT:为了使派生类的第一个成员成为其基类,它必须有两个东西:基类和成员。由于层次结构中只有一个类可以有成员,并且仍然是标准布局,这意味着它的基类不能有成员。@AndyT,是的,IME,关于别名规则,你基本上是正确的。同一类型的两个不同实例需要有不同的内存地址。这允许使用内存地址跟踪对象标识。基对象和第一个派生成员是不同的实例,因此它们必须具有不同的地址,这会强制添加填充,从而影响类的布局。如果它们是不同类型的,那也没关系;例如,允许不同类型的对象在类及其第一个数据成员中具有相同的地址。有人建议允许聚合具有基类,只要它是默认可构造的。请参见,而POD可能保持不变,C++14 StandardLayoutType,这是POD的一项要求,已根据cppref进行更改:@CiroSantilli新疆改造中心六四事件法轮功 谢谢你,我不知道我怎么会错过这些,我将在接下来的几天内尝试更新。请告诉我你是否能想出一个在C++14中是POD但在C++11中不是POD的示例:-我已经在@CiroSantilli上开始了一个详细的示例列表新疆改造中心六四事件法轮功 所以这里发生的事情是,如果我们看中的标准布局描述,它们匹配。这些更改通过缺陷报告应用到C++14。因此,当我最初写这篇文章时,它是正确的:-p什么是POD子集:可以说这些定义背后的动机大致是:POD==memcpy'able,Aggregate==Aggregate initializable?注意,我刚刚更新了缺陷的标准布局更改,这意味着它们实际上应用于C++14。这就是为什么我的答案没有包括他们b/c这发生在我写了我的答案之后。注意,我在这个问题上开始悬赏。谢谢@ShafikYaghmour。我将审查缺陷报告状态并相应地修改我的答案。@ShafikYaghmour,在对C++14过程进行了一些审查之后,我认为,虽然这些DRs在2014年6月的Rapperswil会议上被接受,但2014年2月的Issaquah会议上的语言成为了C++14。根据ISO规则,我们没有正式批准任何编辑到C++工作文件。我遗漏了什么吗?他们有“CD4”状态,这意味着他们应该在C++14模式下应用。虽然我会提高投票率,但添加这个确实感觉有点早,我不知道在C++2x完成之前会有什么重大改变。@ShafikYaghmour是的,可能太早了。但考虑到SD是新语言功能的最后期限,我只知道这两个正在运行中的功能——最糟糕的情况是我稍后会阻止删除其中一个部分?我刚刚看到悬赏案的问题很活跃,我想在我忘记之前,这是一个插话的好时机。我明白,我已经因为类似的案件被诱惑过好几次了。我总是担心一些重要的事情会发生变化,最终我会不得不重写它。@ShafikYaghmour这里似乎什么都不会改变:你应该在指定的初始值设定项上添加一些单词
struct A
{
  int a = 3;
  int b = 3;
};
struct B1 // not a aggregate
{
    int i1;
    B1(int a) : i1(a) { }
};
struct B2
{
    int i2;
    B2() = default;
};
struct M // not an aggregate
{
    int m;
    M(int a) : m(a) { }
};
struct C : B1, B2
{
    int j;
    M m;
    C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};
struct B { int i; }; // standard-layout class
struct C : B { }; // standard-layout class
struct D : C { }; // standard-layout class
struct E : D { char : 4; }; // not a standard-layout class
struct Q {};
struct S : Q { };
struct T : Q { };
struct U : S, T { }; // not a standard-layout class
struct X {
    X() = delete;
};
class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error
template <typename T> Point(T, T) -> Point<T>;