您的位置:首页 > 编程语言 > C语言/C++

C++中的Plain Old Data(POD)

2016-10-19 15:22 429 查看


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


What about PODs?

PODs went through a lot of changes. Lots of previous rules about PODs were relaxed in this new standard, and the way the definition is provided in the standard was radically changed.

The idea of a POD is to capture basically two distinct properties:
It supports static initialization, and
Compiling a POD in C++ gives you the same memory layout as a struct compiled in C.

Because of this, the definition has been split into two distinct concepts: trivial classes and standard-layout classes, because these are more
useful than POD. The standard now rarely uses the term POD, preferring the more specific trivial and standard-layout concepts.

The new definition basically says that a POD is a class that is both trivial and has standard-layout, and this property must hold recursively for all non-static data members:

A POD struct is a non-union class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). Similarly, a POD union is a union that is both a trivial class and
a standard layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). A POD class is a class that is either a POD struct or a POD union.

Let's go over each of these two properties in detail separately.


Trivial classes

Trivial is the first property mentioned above: trivial classes support static initialization. If a class is trivially copyable (a superset of trivial classes), it is ok to copy its representation over
the place with things like 
memcpy
 and
expect the result to be the same.

The standard defines a trivial class as follows:

A trivially copyable class is a class that:

— has no non-trivial copy constructors (12.8),

— has no non-trivial move constructors (12.8),

— has no non-trivial copy assignment operators (13.5.3, 12.8),

— has no non-trivial move assignment operators (13.5.3, 12.8), and

— has a trivial destructor (12.4).

A trivial class is a class that has a trivial default constructor (12.1) and is trivially copyable.

[ Note: In particular, a trivially copyable or trivial class does not have virtual functions or virtual base classes.—end note ]

So, what are all those trivial and non-trivial things?

A copy/move constructor for class X is trivial if it is not user-provided and if

— class X has no virtual functions (10.3) and no virtual base classes (10.1), and

— the constructor selected to copy/move each direct base class subobject is trivial, and

— for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;

otherwise the copy/move constructor is non-trivial.

Basically this means that a copy or move constructor is trivial if it is not user-provided, the class has nothing virtual in it, and this property holds recursively for all the members of the class and for the base class.

The definition of a trivial copy/move assignment operator is very similar, simply replacing the word "constructor" with "assignment operator".

A trivial destructor also has a similar definition, with the added constraint that it can't be virtual.

And yet another similar rule exists for trivial default constructors, with the addition that a default constructor is not-trivial if the class has non-static data members with brace-or-equal-initializers,
which we've seen above.

Here are some examples to clear everything up:
// 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
};


Standard-layout

Standard-layout is the second property. The standard mentions that these are useful for communicating with other languages, and that's because a standard-layout class has the same memory layout of the
equivalent C struct or union.

This is another property that must hold recursively for members and all base classes. And as usual, no virtual functions or virtual base classes are allowed. That would make the layout incompatible with C.

A relaxed rule here is that standard-layout classes must have all non-static data members with the same access control. Previously these had to be all public, but now you can make them private or protected,
as long as they are all private or all protected.

When using inheritance, only one class in the whole inheritance tree can have non-static data members, and the first non-static data member cannot be of a base class type (this could break aliasing rules),
otherwise, it's not a standard-layout class.

This is how the definition goes in the standard text:

A standard-layout class is a class that:

— has no non-static data members of type non-standard-layout class (or array of such types) or reference,

— has no virtual functions (10.3) and no virtual base classes (10.1),

— has the same access control (Clause 11) for all non-static data members,

— has no non-standard-layout base classes,

— either has no non-static data members in the most derived class and at most one base class with non-static data members, or has no base classes with non-static data members, and

— has no base classes of the same type as the first non-static data member.

A standard-layout struct is a standard-layout class defined with the class-key struct or the class-key class.

A standard-layout union is a standard-layout class defined with the class-key union.

[ Note: Standard-layout classes are useful for communicating with code written in other programming languages. Their layout is specified in 9.2.—end note ]

And let's see a few examples.
// 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


Conclusion

With these new rules a lot more types can be PODs now. And even if a type is not POD, we can take advantage of some of the POD properties separately (if it is only one of trivial or standard-layout).

The standard library has traits to test these properties in the header 
<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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: