首页 文章

什么是聚合和POD以及它们如何/为何特殊?

提问于
浏览
470

FAQ是关于聚合和POD的,涵盖以下材料:

  • Aggregates 是什么?

  • 什么是 POD s(普通旧数据)?

  • 他们有什么关系?

  • 他们如何以及为何特别?

  • C 11有什么变化?

5 回答

  • 494

    如何阅读:

    这篇文章相当长 . 如果您想了解聚合和POD(普通旧数据)需要时间并阅读它 . 如果您只对聚合感兴趣,请阅读第一部分 . 如果您只对POD感兴趣,那么您必须首先阅读聚合的定义,含义和示例,然后您可以跳转到POD但我仍然建议您完整阅读第一部分 . 聚合的概念对于定义POD至关重要 . 如果您发现任何错误(包括语法,文体,格式,语法等),请发表评论,我将进行编辑 .

    什么是聚合以及它们为何特殊

    Formal definition from the C++ standard (C++03 8.5.1 §1)

    聚合是一个数组或类(第9节),没有用户声明的构造函数(12.1),没有私有或受保护的非静态数据成员(第11节),没有基类(第10节),也没有虚函数( 10.3) .

    那么,好吧,让它们聚合起来吧?是的他们可以 . 在C中,术语 class 指的是所有类,结构和联合 . 因此,当且仅当它满足上述定义中的条件时,类(或结构或联合)才是聚合 . 这些标准意味着什么?

    • 这并不意味着聚合类不能有构造函数,实际上只要编译器隐式声明它们就可以有默认构造函数和/或复制构造函数,而不是用户明确声明

    • 没有私人或受保护的 non-static data members . 您可以拥有任意数量的私有和受保护的成员函数(但不是构造函数)以及任意数量的私有或受保护的 static 数据成员和成员函数,并且不违反聚合类的规则

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

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

    现在让我们看一些例子:

    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
    };
    

    你明白了 . 现在让我们看看聚合是如何特殊的 . 与非聚合类不同,它们可以使用花括号 {} 进行初始化 . 这种初始化语法通常用于数组,我们只是了解到这些是聚合 . 那么,让我们从他们开始吧 .

    Type array_name[n] = {a1, a2, …, am};

    if(m == n)
    数组的第i个元素用ai初始化
    else if(m < n)
    数组的前m个元素用a1,a2,...,am初始化,如果可能的话,其他 n - m 元素是值初始化的(参见下面的术语解释)
    else if(m > n)
    编译器会发出错误
    else (在没有指定n的情况下就是这种情况,如 int a[] = {1, 2, 3};
    假设数组(n)的大小等于m,所以 int a[] = {1, 2, 3}; 相当于 int a[3] = {1, 2, 3};

    当标量类型的对象( boolintchardouble ,指针等)进行值初始化时,表示该类型的 0 初始化( boolbool0.0double 等) . 当具有用户声明的默认构造函数的类类型的对象被初始化时,将调用其默认构造函数 . 如果隐式定义了默认构造函数,则递归地对所有非静态成员进行值初始化 . 这个定义不精确,有点不正确但它应该给你基本的想法 . 引用不能进行值初始化 . 例如,如果类没有适当的默认构造函数,则非聚合类的值初始化可能会失败 .

    数组初始化的示例:

    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.i110y.x.i220y.i[0]20y.i[1]30y.f 进行了值初始化,即使用 0.0 初始化 . 受保护的静态成员 d 根本没有初始化,因为它是 static .

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

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

    关于聚合的足够多 . 现在我们可以定义更严格的类型集,即POD

    什么是POD以及为什么它们是特殊的

    Formal definition from the C++ standard (C++03 9 §4)

    POD-struct是一个聚合类,它没有非POD-struct类型的非静态数据成员,非POD-union(或此类型的数组)或引用,并且没有用户定义的复制赋值运算符和没有用户定义的析构函数 . 类似地,POD-union是一个聚合联合,它没有非POD结构类型的非静态数据成员,非POD联合(或此类类型的数组)或引用,并且没有用户定义的副本赋值运算符并且没有用户定义的析构函数 . POD类是POD结构或POD结合的类 .

    哇,这个解析起来比较困难,不是吗? :)让我们离开工会(与上面相同的理由)并以更清晰的方式改写:

    如果聚合类没有用户定义的复制赋值运算符和析构函数,并且其非静态成员都不是非POD类,非POD数组或引用,则称为POD .

    这个定义意味着什么? (我提到 POD 代表 Plain Old Data ?)

    • 所有POD类都是聚合,或者,换句话说,如果一个类不是聚合,那么肯定不是POD

    • 类,就像结构一样,可以是POD,即使标准术语是两种情况下的POD结构

    • 就像聚合的情况一样,这个类的静态成员并不重要

    例子:

    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
    };
    

    POD类,POD联合,标量类型和此类类型的数组统称为 POD-types.
    POD在很多方面都很特别 . 我只提供一些例子 .

    • POD类最接近C结构 . 与它们不同,POD可以具有成员函数和任意静态成员,但这两者都不会改变对象的内存布局 . 因此,如果您想编写一个可以在C甚至.NET中使用的或多或少可移植的动态库,您应该尝试使所有导出的函数只接受POD类型的参数 .

    • 非POD类类型的对象的生命周期在构造函数完成时开始,在析构函数完成时结束 . 对于POD类,生命周期在对象的存储被占用时开始,并在该存储被释放或重用时结束 .

    • 对于POD类型的对象,标准保证当您将对象的内容放入char或unsigned char数组中,然后 memcpy 将内容返回到您的对象时,该对象将保持其原始值 . 请注意,对于非POD类型的对象,不存在此类保证 . 此外,您可以使用 memcpy 安全地复制POD对象 . 以下示例假定T是POD类型:

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
    
    • goto声明 . 您可能知道,从某个变量尚未在范围内的点到已经在范围内的点,通过goto进行跳转是非法的(编译器应该发出错误) . 仅当变量为非POD类型时,此限制才适用 . 在下面的示例中, f() 格式不正确,而 g() 格式正确 . 请注意,Microsoft的编译器对此规则过于宽松 - 它只是在两种情况下都会发出警告 .
    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类型,则可以安全 A*A*T* 并获得指向第一个成员的指针,反之亦然 .

    名单不断......

    结论

    重要的是要了解POD到底是什么,因为正如您所看到的,许多语言功能对它们的行为都不同 .

  • 80

    C 11有什么变化?

    聚合

    聚合的标准定义略有改变,但它仍然几乎相同:

    聚合是一个数组或类(第9节),没有用户提供的构造函数(12.1),非静态数据成员(9.2)没有大括号或等于初始值,没有私有或受保护的非静态数据成员(第11条),没有基类(第10条),没有虚函数(10.3) .

    好的,改变了什么?

    • 以前,聚合可能没有用户声明的构造函数,但现在它不能拥有用户提供的构造函数 . 有区别吗?是的,有,因为现在你可以声明构造函数并默认它们:
    struct Aggregate {
        Aggregate() = default; // asks the compiler to generate the default implementation
    };
    

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

    • 现在聚合不能为非静态数据成员提供任何大括号或等于初始值的方法 . 这是什么意思?嗯,这只是因为有了这个新标准,我们可以直接在类中初始化成员,如下所示:
    struct NotAggregate {
        int x = 5; // valid in C++11
        std::vector<int> s{1,2,3}; // also valid
    };
    

    使用此功能使该类不再是聚合,因为它基本上等同于提供您自己的默认构造函数 .

    那么,什么是聚合并没有太大变化 . 它仍然是相同的基本想法,适应新功能 .

    POD怎么样?

    POD经历了很多变化 . 在这个新标准中放宽了许多关于POD的先前规则,并且标准中提供的定义方式发生了根本改变 .

    POD的想法是捕获基本上两个不同的属性:

    • 它支持静态初始化,和

    • 在C中编译POD为您提供与在C中编译的结构相同的内存布局 .

    因此,定义被分为两个不同的概念:普通类和标准布局类,因为它们比POD更有用 . 该标准现在很少使用术语POD,更喜欢更具体的琐碎和标准布局概念 .

    新定义基本上说POD是一个既简单又具有标准布局的类,并且此属性必须递归保存所有非静态数据成员:

    POD结构是一个非联合类,它既是一个普通类,也是一个标准布局类,并且没有非POD结构类型的非静态数据成员,非POD联合(或这类类型的数组) . 类似地,POD联合是一个简单类和标准布局类的联合,并且没有非POD结构类型的非静态数据成员,非POD联合(或这种类型的数组) . POD类是POD结构或POD联合的类 .

    让我们分别详细介绍这两个属性中的每一个 .

    琐碎的课程

    Trivial是上面提到的第一个属性:普通类支持静态初始化 . 如果一个类是可以轻易复制的(普通类的超集),可以使用 memcpy 这样的地方复制其表示,并期望结果相同 .

    该标准定义了一个简单的类,如下所示:

    一个简单的可复制类是一个类: - 没有非平凡的复制构造函数(12.8), - 没有非平凡的移动构造函数(12.8), - 没有非平凡的复制赋值运算符(13.5.3,12.8) , - 没有非平凡的移动赋值运算符(13.5.3,12.8),并且 - 有一个简单的析构函数(12.4) . 一个普通的类是一个具有普通默认构造函数(12.1)并且可以轻易复制的类 . [注意:特别是,一个简单的可复制或普通的类没有虚函数或虚基类.-结束语]

    那么,那些琐碎和琐碎的事情是什么呢?

    如果类X不是用户提供的,那么类X的复制/移动构造函数是微不足道的 - 如果 - 类X没有虚函数(10.3)而没有虚函数基类(10.1),并且 - 构造函数选择复制/移动每个直接基类子对象是微不足道的,并且 - 对于类类型(或其数组)的X的每个非静态数据成员,选择复制/移动该成员的构造函数是微不足道的;否则复制/移动构造函数是非平凡的 .

    基本上这意味着如果复制或移动构造函数不是用户提供的,则该复制或移动构造函数是微不足道的,该类中没有虚拟内容,并且此属性以递归方式保存在类的所有成员和基类中 .

    简单的复制/移动赋值运算符的定义非常相似,只需将“构造函数”替换为“赋值运算符” .

    一个简单的析构函数也有一个类似的定义,增加的约束是它不能是虚拟的 .

    还有另一个类似的规则存在于普通的默认构造函数中,另外如果类具有带有大括号或者相等初始值的非静态数据成员,则默认构造函数并不重要,我们已在上面看到过 .

    以下是一些清除所有内容的示例:

    // empty classes are trivial
    struct Trivial1 {};
    
    // all special members are implicit
    struct Trivial2 {
        int x;
    };
    
    struct Trivial3 : Trivial2 { // base class is trivial
        Trivial3() = default; // not a user-provided ctor
        int y;
    };
    
    struct Trivial4 {
    public:
        int a;
    private: // no restrictions on access modifiers
        int b;
    };
    
    struct Trivial5 {
        Trivial1 a;
        Trivial2 b;
        Trivial3 c;
        Trivial4 d;
    };
    
    struct Trivial6 {
        Trivial2 a[23];
    };
    
    struct Trivial7 {
        Trivial6 c;
        void f(); // it's okay to have non-virtual functions
    };
    
    struct Trivial8 {
         int x;
         static NonTrivial1 y; // no restrictions on static members
    };
    
    struct Trivial9 {
         Trivial9() = default; // not user-provided
          // a regular constructor is okay because we still have default ctor
         Trivial9(int x) : x(x) {};
         int x;
    };
    
    struct NonTrivial1 : Trivial3 {
        virtual void f(); // virtual members make non-trivial ctors
    };
    
    struct NonTrivial2 {
        NonTrivial2() : z(42) {} // user-provided ctor
        int z;
    };
    
    struct NonTrivial3 {
        NonTrivial3(); // user-provided ctor
        int w;
    };
    NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
                                          // still counts as user-provided
    struct NonTrivial5 {
        virtual ~NonTrivial5(); // virtual destructors are not trivial
    };
    

    标准布局

    标准布局是第二个属性 . 标准提到这些对于与其他语言通信很有用,这是因为标准布局类具有与等效C结构或联合相同的内存布局 .

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

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

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

    这就是定义在标准文本中的用法:

    标准布局类是一个类: - 没有类型非标准布局类(或此类类型的数组)或引用的非静态数据成员, - 没有虚函数(10.3)且没有虚基类(10.1), - 对所有非静态数据成员具有相同的访问控制(第11条), - 没有非标准布局基类, - 在最派生类中没有非静态数据成员,并且最多一个具有非静态数据成员的基类,或者没有带有非静态数据成员的基类,并且 - 没有与第一个非静态数据成员相同类型的基类 . 标准布局结构是使用类键结构或类键类定义的标准布局类 . 标准布局联合是使用类 - 键联合定义的标准布局类 . [注意:标准布局类对于与使用其他编程语言编写的代码进行通信非常有用 . 它们的布局在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属性(如果它只是一个简单的布局或标准布局) .

    标准库具有在 Headers <type_traits> 中测试这些属性的特征:

    template <typename T>
    struct std::is_pod;
    template <typename T>
    struct std::is_trivial;
    template <typename T>
    struct std::is_trivially_copyable;
    template <typename T>
    struct std::is_standard_layout;
    
  • 401

    C 14有什么变化

    我们可以参考Draft C++14 standard以供参考 .

    聚合

    这将在 8.5.1 聚合部分中介绍,它给出了以下定义:

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

    唯一的变化是现在添加类内成员初始值设定项不会使类成为非聚合 . 所以C++11 aggregate initialization for classes with member in-pace initializers中的以下示例:

    struct A
    {
      int a = 3;
      int b = 3;
    };
    

    不是C 11中的聚合,但是它在C 14中 . 这个改变包含在_1353531中,其中有以下摘要:

    Bjarne Stroustrup和Richard Smith提出了一个关于聚合初始化和成员初始化器不能协同工作的问题 . 本文提出通过采用史密斯提出的措辞来解决这个问题,该措辞消除了聚合不能拥有成员初始化者的限制 .

    POD保持不变

    POD(普通旧数据)结构的定义在 9 类中介绍,该类说:

    POD struct110是一个非联合类,它既是普通类,也是标准布局类,并且没有非POD结构,非POD联合(或此类类型的数组)类型的非静态数据成员 . 同样,POD联盟也是一个联盟普通类和标准布局类,并且没有非POD结构类型的非静态数据成员,非POD联合(或此类类型的数组) . POD类是POD结构或POD联合的类 .

    这与C 11的措辞相同 .

    C 14的标准布局更改

    正如评论中所指出的那样,pod依赖于标准布局的定义,并且确实在C 14中发生了变化,但这是通过在事实之后应用于C 14的缺陷报告 .

    有三个DR:

    所以standard-layout从这个Pre C 14开始:

    标准布局类是一个类:(7.1)没有类型非标准布局类(或此类类型的数组)或引用的非静态数据成员,(7.2)没有虚函数([class . 虚拟])并且没有虚拟基类([class.mi]),(7.3)对所有非静态数据成员具有相同的访问控制(Clause [class.access]),(7.4)没有非标准布局基类,(7.5)在大多数派生类中没有非静态数据成员,最多只有一个具有非静态数据成员的基类,或者没有带有非静态数据成员的基类,并且(7.6)没有与第一个非静态数据成员相同类型的基类

    this in C++14

    类S是标准布局类,如果它:(3.1)没有非标准布局类(或类型的数组)或引用类型的非静态数据成员,(3.2)没有虚函数,没有虚拟基类,(3.3)对所有非静态数据成员具有相同的访问控制,(3.4)没有非标准布局基类,(3.5)最多有一个任何给定类型的基类子对象,(3.6) )具有类中的所有非静态数据成员和位字段及其基类首先在同一个类中声明,并且(3.7)没有类型的集合M(S)的元素作为基类,其中任何类类型X,M(X)定义如下.104 [注意:M(X)是X中零偏移的所有非基类子对象的类型集 . - 尾注](3.7) .1)如果X是非联合类类型,没有(可能是继承的)非静态数据成员,则集合M(X)为空 . (3.7.2)如果X是非联合类类型,其类型为X0的非静态数据成员,其大小为零或者是X的第一个非静态数据成员(其中所述成员可能是匿名联合),集合M(X)由X0和M(X0)的元素组成 . (3.7.3)如果X是联合类型,则集合M(X)是所有M(Ui)和包含所有Ui的集合的并集,其中每个Ui是X的第i个非静态数据成员的类型 . . (3.7.4)如果X是元素类型为Xe的数组类型,则集合M(X)由Xe和M(Xe)的元素组成 . (3.7.5)如果X是非类非数组类型,则集合M(X)为空 .

  • 11

    请你详细说明以下规则:

    我会尽力:

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

    这很简单:所有非静态数据成员都必须是 publicprivateprotected . 你不能有一些 public 和一些 private .

    对它们的推理归结为完全区分“标准布局”和“非标准布局”的原因 . 也就是说,让编译器可以自由选择如何将内容放入内存中 . 这不仅仅是关于vtable指针 .

    当他们在98中标准化C时,他们必须基本预测人们将如何实现它 . 虽然他们对各种C语言有相当多的实施经验,但他们对事情并不确定 . 所以他们决定保持谨慎:给编译器尽可能多的自由 .

    这就是为什么C 98中POD的定义如此严格 . 它使C编译器在大多数类的成员布局上具有很大的自由度 . 基本上,POD类型是特殊情况,特别是你出于某种原因写的 .

    当C 11正在开发时,他们对编译器有了更多的经验 . 他们意识到...... C编译器编写者真的很懒惰 . 他们拥有所有这些自由,但他们没有采取任何行动 .

    标准布局的规则或多或少地编纂了通常的做法:大多数编译器实际上并没有真正改变它们(对于相应的类型特征可能有些东西) .

    现在,当谈到 public / private 时,事情就不同了 . 重新排序哪些成员 publicprivate 的自由实际上对编译器很重要,特别是在调试版本中 . 而且自标准布局以来是否与其他语言兼容,您不能在调试与发布中使用不同的布局 .

    然后有's the fact that it doesn' t真的伤害了用户 . 如果你正在制作一个封装的类,那么你的所有数据成员都可能是 private . 您通常不会在完全封装的类型上公开公共数据成员 . 因此,对于那些想要这样做的少数用户来说,这只会是一个问题 .

    所以这不是什么大损失 .

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

    这个问题的原因可以追溯到为什么他们再次标准化标准布局:通常的做法 .

    当涉及到实际存储事物的继承树的两个成员时,没有通用的做法 . 有些人把基类放在派生之前,有些则用另一种方式做 . 如果成员来自两个基类,您会以哪种方式订购?等等 . 编译器在这些问题上存在很大分歧 .

    此外,由于零/一/无穷大规则,一旦你说你可以有两个成员类,你可以说你想要多少 . 这需要添加许多布局规则来处理这个问题 . 你必须说多重继承是如何工作的,哪些类把它们的数据放在其他类之前等等 . 这是很多规则,因为很少有物质增益 .

    您不能制作没有虚函数和默认构造函数标准布局的所有内容 .

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

    我真的不能跟这个说话 . 在C的别名规则中,我没有受过足够的教育才能真正理解它 . 但它与基本成员将与基类本身共享相同地址这一事实有关 . 那是:

    struct Base {};
    struct Derived : Base { Base b; };
    
    Derived d;
    static_cast<Base*>(&d) == &d.b;
    

    这可能是针对C的别名规则 . 某种程度上来说 .

    但是,请考虑一下:实际上有能力做到这一点有多大用处?由于只有一个类可以拥有非静态数据成员,因此 Derived 必须是该类(因为它具有 Base 作为成员) . 所以 Base 必须为空(数据) . 如果 Base 是空的,还有一个基类...为什么要有一个数据成员呢?

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

    再说一次:没有大的损失 .

  • 37

    C 17的变化

    下载C 17国际标准最终草案here .

    Aggregates

    C 17扩展并增强了聚合和聚合初始化 . 标准库现在还包含 std::is_aggregate 类型的特征类 . 以下是第11.6.1.1节和第11.6.1.2节中的正式定义(内部参考文献已省略):

    聚合是一个数组或类 - 没有用户提供的,显式的或继承的构造函数, - 没有私有或受保护的非静态数据成员, - 没有虚函数, - 没有虚拟,私有或受保护的基类 . [注意:聚合初始化不允许访问受保护和私有基类的成员或构造函数 . -end note]聚合的元素是: - 对于数组,数组元素按增加的下标顺序,或 - 对于类,按声明顺序的直接基类,后跟非直接的非静态数据成员声明顺序中的匿名工会成员 .

    改变了什么?

    • 聚合现在可以拥有公共的非虚拟基类 . 此外,并不要求基类是聚合 . 如果它们不是聚合,则列表初始化 .
    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::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;
    };
    

    Trivial Classes

    在C17中重新设计了琐碎类的定义,以解决C14中未涉及的几个缺陷 . 这些变化本质上是技术性的 . 这是12.0.6的新定义(内部参考文献省略):

    一个简单的可复制类是一个类: - 其中每个复制构造函数,移动构造函数,复制赋值运算符和移动赋值运算符被删除或无关紧要, - 至少有一个未删除的复制构造函数,移动构造函数,复制赋值运算符,或移动赋值运算符,并且 - 具有一个简单的,未删除的析构函数 . 一个普通的类是一个可以轻易复制的类,它有一个或多个默认的构造函数,所有这些都是微不足道的或删除的,并且至少有一个不被删除 . [注意:在特别是,一个简单的可复制或普通的类没有虚函数或虚基类.-结束语]

    变化:

    • 在C14下,对于一个简单的类,该类不能有任何非平凡的复制/移动构造函数/赋值运算符 . 但是,隐式声明为默认的构造函数/运算符可能是非平凡的,但仍定义为已删除,因为例如,类包含无法复制/移动的类类型的子对象 . 这种非平凡的,定义为删除的构造函数/运算符的存在将导致整个类是非平凡的 . 析构函数存在类似的问题 . C 17阐明了这样的构造函数/运算符的存在不会导致类非平凡地可复制,因此非平凡,并且一个简单的可复制类必须具有一个简单的,未删除的析构函数 . DR1734DR1928

    • C 14允许一个简单的可复制类,因此是一个简单的类,将每个复制/移动构造函数/赋值运算符声明为已删除 . 如果类也是标准布局,则可以使用 std::memcpy 合法地复制/移动它 . 这是一个语义上的矛盾,因为通过将所有构造函数/赋值运算符定义为已删除,该类的创建者明确表示该类无法复制/移动,但该类仍然满足了一个简单的可复制类的定义 . 因此,在C17中,我们有一个新的子句,声明平凡的可复制类必须至少有一个平凡的,未删除的(虽然不一定是公共可访问的)复制/移动构造函数/赋值运算符 . 见N4148DR1734

    • 第三个技术变化涉及默认构造函数的类似问题 . 在C 14下,一个类可以有一些简单的默认构造函数,它们被隐式定义为已删除,但仍然是一个普通的类 . 新定义阐明了一个普通的类必须至少有一个简单的,未删除的默认构造函数 . 见DR1496

    Standard-layout Classes

    标准布局的定义也被重新设计以解决缺陷报告 . 这些变化再次是技术性的 . 这是标准(12.0.7)中的文字 . 和以前一样,内部引用被省略了:

    类S是标准布局类,如果它: - 没有非标准布局类(或类型的数组)或引用类型的非静态数据成员, - 没有虚函数,没有虚基类, - 对所有非静态数据成员具有相同的访问控制, - 没有非标准布局基类, - 最多具有任何给定类型的一个基类子对象, - 具有所有非静态数据成员和位域在类及其基类中首先在同一个类中声明,并且 - 没有类型的集合M(S)的元素(在下面定义)作为基类.108 M(X)定义如下: - 如果X是一个非联合类类型,没有(可能是继承的)非静态数据成员,集合M(X)为空 . - 如果X是非联合类类型,其第一个非静态数据成员具有类型X0(其中所述成员可以是匿名联合),则集合M(X)由X0和M(X0)的元素组成 . - 如果X是联合类型,则集合M(X)是所有M(Ui)和包含所有Ui的集合的并集,其中每个Ui是X的第i个非静态数据成员的类型 . - 如果X是一个元素类型为Xe的数组类型,集合M(X)由Xe和M(Xe)元素组成 . - 如果X是非类非数组类型,则集合M(X)为空 . [注意:M(X)是所有非基类子对象的类型的集合,它们在标准布局类中保证在X中处于零偏移.-末端注释] [示例:struct B {int一世; }; //标准布局类
    struct C:B {}; //标准布局类
    结构D:C {}; //标准布局类
    struct E:D {char:4; }; //不是标准布局类
    struct Q {};
    struct S:Q {};
    struct T:Q {};
    struct U:S,T {}; //不是标准布局类 - 末端示例] 108)这确保了具有相同类类型且属于同一最派生对象的两个子对象不在同一地址分配 .

    变化:

    • 澄清了这样的要求:派生树中只有一个类"has"非静态数据成员引用首先声明此类数据成员的类,而不是可以继承它们的类,并将此要求扩展到非静态位字段 . 还澄清了标准布局类"has at most one base class subobject of any given type."见DR1813DR1881

    • 标准布局的定义从未允许过任何基类的类型与第一个非静态数据成员的类型相同 . 这是为了避免偏移零的数据成员与任何基类具有相同类型的情况 . C 17标准提供了更严格的递归定义"the set of the types of all non-base-class subobjects that are guaranteed in a standard-layout class to be at a zero offset",以便禁止这些类型成为任何基类的类型 . 见DR1672DR2120 .

相关问题