什么是聚合和POD以及如何/为什么它们特别? [英] What are Aggregates and PODs and how/why are they special?

查看:113
本文介绍了什么是聚合和POD以及如何/为什么它们特别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

常见问题适用于汇总和POD,并涵盖以下资料:




  • 什么是汇总

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

  • 它们如何相关?

  • 他们如何和为什么特别?

  • C ++ 11有什么变化?


解决方案

h1>如何阅读:

本文篇幅较长。如果你想知道聚合和POD(普通旧数据)需要时间并且读取它。如果你只关注聚合,只读第一部分。如果您只对POD感兴趣,那么您必须首先阅读聚合的定义,含义和示例,然后您可以跳转到POD,但我仍然建议您完整阅读第一部分。聚集的概念对于定义POD是必要的。如果您发现任何错误(甚至很小,包括语法,文体,格式,语法等),请留言,我将编辑。



为什么它们是特殊的



C ++标准的定义( C ++ 03 8.5.1§1


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


那么,好吧,让我们解析这个定义。首先,任何数组是一个聚合。如果...等待,类也可以是聚合。没有什么关于结构或联合,不能他们是聚合?是的他们可以。在C ++中,术语 class 指所有类,结构和联合。因此,类(或结构或联合)是一个聚合,只有当它满足上述定义的条件。这些标准意味着什么?




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


  • 没有私有或受保护 非静态数据成员 。您可以拥有尽可能多的私有和受保护的成员函数(但不是构造函数)以及许多私有或受保护的 静态 数据成员和成员函数,聚合类的规则


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

    >
  • 数组是一个聚合,即使它是一个非聚合类类型的数组。




现在让我们来看一些例子:

  class NotAggregate1 
{
virtual void f(){} //记住?没有虚函数
};

class NotAggregate2
{
int x; // x是默认私有的和非静态的
};

class NotAggregate3
{
public:
NotAggregate3(int){} // oops,用户定义的构造函数
};

class Aggregate1
{
public:
NotAggregate1 member1; // ok,public member
Aggregate1& operator =(Aggregate1 const& rhs){/ * * /} // ok,copy-assignment
private:
void f(){} // ok,只是一个私有函数
};

你的想法。现在让我们看看聚合是如何特殊的。与非聚合类不同,它们可以用大括号 {} 初始化。这种初始化语法对于数组通常是已知的,我们只是得知这些是聚合。因此,让我们从他们开始。



类型array_name [n] = {a 1 2 = n)

数组的第i个 th 元素会以 i

else if(m

数组的前m个元素用 1 如果可能的话, 元素
2 ,..., m ,另一个 n-m -initialized (请参阅下文中有关该术语的解释)

else if(m> n)

编译器将发出错误

else (这种情况下,n不是指定的 int a [] = {1,2,3};

数组(n)的大小假设等于m,所以 int a [] = {1,2,3}; 等价于 int a [3] = {1,2,3}



当标量类型的对象( bool int char double ,指针等) ( false for )初始化为 0 bool 0.0 double 等)。当具有用户声明的默认构造函数的类类型的对象被初始化时,它的默认构造函数被调用。如果默认构造函数被隐式定义,则所有非静态成员被递归地初始化。这个定义是不精确的,有点不正确,但它应该给你的基本想法。引用不能进行值初始化。



数组初始化的示例:

$ b。例如,如果类没有合适的默认构造函数,则非聚合类的值初始化可能失败。
$ b

  class A 
{
public:
A(int){} //无默认构造函数
};
class B
{
public:
B(){} //默认构造函数可用
};
int main()
{
A a1 [3] = {A(2),A(1),A(14)}; // OK n == m
A a2 [3] = {A(2)}; //错误A没有默认构造函数。无法初始化a2 [1]和a2 [2]
B b1 [3] = {B()}; // OK b1 [1]和b1 [2]是值初始化的,在这种情况下使用default-ctor
int Array1 [1000] = {0}; //所有元素都初始化为0;
int Array2 [1000] = {1}; //注意:只有第一个元素是1,其余的都是0;
bool Array3 [1000] = {}; //大括号也可以为空。所有用false初始化的元素
int Array4 [1000]; //没有初始化。这不同于一个空的{}初始化器,因为
//在这种情况下的元素不是值初始化,但有不确定的值
//(除非,当然,Array4是一个全局数组)
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}};

在上面的示例中, yc 'a' yxi1 10 c $ c> yxi2 20 yi [0] $ c> 20 , yi [1] 30 > yf 是值初始化的,即使用 0.0 初始化。受保护的静态成员 d 未完全初始化,因为它是 static



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



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



聚合的足够了。现在我们可以定义一组更严格的类型,PODs



什么是POD,为什么它们是特殊的



C ++标准的定义( C ++ 03 9§4


A POD-struct是一个聚合类
,它没有
类型的非静态数据成员non-POD-struct,non-POD-union(或
类型)或引用,
没有用户定义的副本赋值
运算符,没有用户定义的
析构函数。类似地,POD-union是
一个没有
的合并联合类型
的非静态数据成员non-POD-struct,non-POD-union(或
array的类型)或引用,
没有用户定义的副本赋值
操作符和没有用户定义的
析构函数。 POD类是一个类
,它是POD-struct或
POD-union。


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


聚合类称为POD如果
没有用户定义的复制赋值
操作符和析构函数,并且其非静态成员的
中没有一个是非POD
类,非POD数组, a
引用。


这个定义是什么意思? (我提到 POD 代表 普通旧数据 ?)




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

  • 即使标准术语对于这两种情况都是POD-struct

  • 就像聚合的情况一样,类的静态成员无关紧要



示例:

  struct POD 
{
int x;
char y;
void f(){} //如果有一个函数没有危害
static std :: vector< char> v; //静态成员不重要
};

struct AggregateButNotPOD1
{
int x;
〜AggregateButNotPOD1(){} //用户定义的析构函数
};

struct AggregateButNotPOD2
{
AggregateButNotPOD1 arrOfNonPod [3]; // array of non-POD class
};

POD类,POD联合,标量类型和此类类型的数组统称为 POD类型。

POD在许多方面都是特殊的。我将仅提供一些示例。




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


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


  • 对于POD类型的对象,标准保证当 memcpy 对象转换为char或unsigned char数组,然后将 memcpy 内容写回您的对象,该对象将保持其原始值。请注意,对于非POD类型的对象没有这样的保证。此外,您可以使用 memcpy 安全地复制POD对象。以下示例假设T是POD类型:

      #define N sizeof(T)
    char buf [N ];
    T obj; // obj初始化为其原始值
    memcpy(buf,& obj,N); //在这两个调用memcpy之间,
    // obj可能被修改
    memcpy(& obj,buf,N); //此时,标量类型
    的obj的每个子对象保持其原始值


  • goto语句。你可能知道,通过goto从一个变量不在范围内的点跳转到它已经在范围内的点是非法的(编译器应该发出一个错误)。此限制仅适用于变量为非POD类型的情况。在下面的示例中, f()是错误的,而 g()注意,微软的编译器对于这个规则过于宽松,它只是在这两种情况下发出警告。

      int f $ b {
    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 * 中安全地 reinterpret_cast




此列表继续...



结论



一个POD是因为许多语言功能,如你所见,对他们表现不同。


This FAQ is about Aggregates and PODs and covers the following material:

  • What are Aggregates?
  • What are PODs (Plain Old Data)?
  • How are they related?
  • How and why are they special?
  • What changes for C++11?

解决方案

How to read:

This article is rather long. If you want to know about both aggregates and PODs (Plain Old Data) take time and read it. If you are interested just in aggregates, read only the first part. If you are interested only in PODs then you must first read the definition, implications, and examples of aggregates and then you may jump to PODs but I would still recommend reading the first part in its entirety. The notion of aggregates is essential for defining PODs. If you find any errors (even minor, including grammar, stylistics, formatting, syntax, etc.) please leave a comment, I'll edit.

What are aggregates and why they are special

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

An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).

So, OK, let's parse this definition. First of all, any array is an aggregate. A class can also be an aggregate if… wait! nothing is said about structs or unions, can't they be aggregates? Yes, they can. In C++, the term class refers to all classes, structs, and unions. So, a class (or struct, or union) is an aggregate if and only if it satisfies the criteria from the above definitions. What do these criteria imply?

  • This does not mean an aggregate class cannot have constructors, in fact it can have a default constructor and/or a copy constructor as long as they are implicitly declared by the compiler, and not explicitly by the user

  • No private or protected non-static data members. You can have as many private and protected member functions (but not constructors) as well as as many private or protected static data members and member functions as you like and not violate the rules for aggregate classes

  • An aggregate class can have a user-declared/user-defined copy-assignment operator and/or destructor

  • An array is an aggregate even if it is an array of non-aggregate class type.

Now let's look at some examples:

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

You get the idea. Now let's see how aggregates are special. They, unlike non-aggregate classes, can be initialized with curly braces {}. This initialization syntax is commonly known for arrays, and we just learnt that these are aggregates. So, let's start with them.

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

if(m == n)
the ith element of the array is initialized with ai
else if(m < n)
the first m elements of the array are initialized with a1, a2, …, am and the other n - m elements are, if possible, value-initialized (see below for the explanation of the term)
else if(m > n)
the compiler will issue an error
else (this is the case when n isn't specified at all like int a[] = {1, 2, 3};)
the size of the array (n) is assumed to be equal to m, so int a[] = {1, 2, 3}; is equivalent to int a[3] = {1, 2, 3};

When an object of scalar type (bool, int, char, double, pointers, etc.) is value-initialized it means it is initialized with 0 for that type (false for bool, 0.0 for double, etc.). When an object of class type with a user-declared default constructor is value-initialized its default constructor is called. If the default constructor is implicitly defined then all nonstatic members are recursively value-initialized. This definition is imprecise and a bit incorrect but it should give you the basic idea. A reference cannot be value-initialized. Value-initialization for a non-aggregate class can fail if, for example, the class has no appropriate default constructor.

Examples of array initialization:

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
}

Now let's see how aggregate classes can be initialized with braces. Pretty much the same way. Instead of the array elements we will initialize the non-static data members in the order of their appearance in the class definition (they are all public by definition). If there are fewer initializers than members, the rest are value-initialized. If it is impossible to value-initialize one of the members which were not explicitly initialized, we get a compile-time error. If there are more initializers than necessary, we get a compile-time error as well.

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

In the above example y.c is initialized with 'a', y.x.i1 with 10, y.x.i2 with 20, y.i[0] with 20, y.i[1] with 30 and y.f is value-initialized, that is, initialized with 0.0. The protected static member d is not initialized at all, because it is static.

Aggregate unions are different in that you may initialize only their first member with braces. I think that if you are advanced enough in C++ to even consider using unions (their use may be very dangerous and must be thought of carefully), you could look up the rules for unions in the standard yourself :).

Now that we know what's special about aggregates, let's try to understand the restrictions on classes; that is, why they are there. We should understand that memberwise initialization with braces implies that the class is nothing more than the sum of its members. If a user-defined constructor is present, it means that the user needs to do some extra work to initialize the members therefore brace initialization would be incorrect. If virtual functions are present, it means that the objects of this class have (on most implementations) a pointer to the so-called vtable of the class, which is set in the constructor, so brace-initialization would be insufficient. You could figure out the rest of the restrictions in a similar manner as an exercise :).

So enough about the aggregates. Now we can define a stricter set of types, to wit, PODs

What are PODs and why they are special

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

A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor. A POD class is a class that is either a POD-struct or a POD-union.

Wow, this one's tougher to parse, isn't it? :) Let's leave unions out (on the same grounds as above) and rephrase in a bit clearer way:

An aggregate class is called a POD if it has no user-defined copy-assignment operator and destructor and none of its nonstatic members is a non-POD class, array of non-POD, or a reference.

What does this definition imply? (Did I mention POD stands for Plain Old Data?)

  • All POD classes are aggregates, or, to put it the other way around, if a class is not an aggregate then it is sure not a POD
  • Classes, just like structs, can be PODs even though the standard term is POD-struct for both cases
  • Just like in the case of aggregates, it doesn't matter what static members the class has

Examples:

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-classes, POD-unions, scalar types, and arrays of such types are collectively called POD-types.
PODs are special in many ways. I'll provide just some examples.

  • POD-classes are the closest to C structs. Unlike them, PODs can have member functions and arbitrary static members, but neither of these two change the memory layout of the object. So if you want to write a more or less portable dynamic library that can be used from C and even .NET, you should try to make all your exported functions take and return only parameters of POD-types.

  • The lifetime of objects of non-POD class type begins when the constructor has finished and ends when the destructor has finished. For POD classes, the lifetime begins when storage for the object is occupied and finishes when that storage is released or reused.

  • For objects of POD types it is guaranteed by the standard that when you memcpy the contents of your object into an array of char or unsigned char, and then memcpy the contents back into your object, the object will hold its original value. Do note that there is no such guarantee for objects of non-POD types. Also, you can safely copy POD objects with memcpy. The following example assumes T is a POD-type:

    #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 statement. As you may know, it is illegal (the compiler should issue an error) to make a jump via goto from a point where some variable was not yet in scope to a point where it is already in scope. This restriction applies only if the variable is of non-POD type. In the following example f() is ill-formed whereas g() is well-formed. Note that Microsoft's compiler is too liberal with this rule—it just issues a warning in both cases.

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

  • It is guaranteed that there will be no padding in the beginning of a POD object. In other words, if a POD-class A's first member is of type T, you can safely reinterpret_cast from A* to T* and get the pointer to the first member and vice versa.

The list goes on and on…

Conclusion

It is important to understand what exactly a POD is because many language features, as you see, behave differently for them.

这篇关于什么是聚合和POD以及如何/为什么它们特别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆