Trivial, standard-layout, and POD types
The latest version of this topic can be found at Trivial, standard-layout, and POD types.
The term layout refers to how the members of an object of class, struct or union type are arranged in memory. In some cases, the layout is well-defined by the language specification. But when a class or struct contains certain C++ language features such as virtual base classes, virtual functions, members with different access control, then the compiler is free to choose a layout. That layout may vary depending on what optimizations are being performed and in many cases object might not even occupy a contiguous area of memory. For example, if a class has virtual functions, all the instances of that class might share a single virtual function table. Such types are of course very useful, but they also have limitations. Because the layout is undefined they cannot be passed to programs written in other languages, such as C, and because they might be non-contiguous they cannot be reliably copied with fast low-level functions such as memcopy
or serialized over a network.
To enable compilers as well as C++ programs and metaprograms to reason about the suitability of any given type for operations that depend on a particular memory layout, C++14 introduced three categories of simple classes and structs: trivial, standard-layout, and POD or Plain Old Data. The Standard Library has the function templates is_trivial<T>
, is_standard_layout<T>
and is_pod<T>
that determine whether a given type belongs to a given category.
Trivial types
When a class or struct in C++ has compiler-provided or explicitly defaulted special member functions, then it is a trivial type. It occupies a contiguous memory area. It can have members with different access specifiers. In C++, the compiler is free to choose how to order members in this situation. Therefore, you can memcopy such objects but you cannot reliably consume them from a C program. A trivial type T can be copied into an array of char or unsigned char, and safely copied back into a T variable. Note that because of alignment requirements, there might be padding bytes between type members.
Trivial types have a trivial default constructor, trivial copy constructor, trivial copy assignment operator and trivial destructor. In each case, trivial means the constructor/operator/destructor is not user-provided and belongs to a class that has
no virtual functions or virtual base classes,
no base classes with a corresponding non-trivial constructor/operator/destructor
no data members of class type with a corresponding non-trivial constructor/operator/destructor
The following examples show trivial types. In Trivial2, the presence of the Trivial2(int a, int b)
constructor requires that you provide a default constructor. For the type to qualify as trivial, you must explicitly default that constructor.
struct Trivial
{
int i;
private:
int j;
};
struct Trivial2
{
int i;
Trivial2(int a, int b) : i(a), j(b) {}
Trivial2() = default;
private:
int j; // Different access control
};
Standard layout types
When a class or struct does not contain certain C++ language features such as virtual functions which are not found in the C language, and all members have the same access control, it is a standard-layout type. It is memcopy-able and the layout is sufficiently defined that it can be consumed by C programs. Standard-layout types can have user-defined special member functions. In addition, standard layout types have these characteristics:
no virtual functions or virtual base classes
all non-static data members have the same access control
all non-static members of class type are standard-layout
any base classes are standard-layout
has no base classes of the same type as the first non-static data member.
meets one of these conditions:
no non-static data member in the most-derived class and no more than one base class with non-static data members, or
has no base classes with non-static data members
The following code shows one example of a standard-layout type:
struct SL
{
// All members have same access:
int i;
int j;
SL(int a, int b) : i(a), j(b) {} // User-defined constructor OK
};
The last two requirements can perhaps be better illustrated with code. In the next example, even though Base is standard-layout, Derived
is not standard layout because both it (the most derived class) and Base
have non-static data members:
struct Base
{
int i;
int j;
};
// std::is_standard_layout<<Derived> == false!
struct Derived : public Base
{
int x;
int y;
};
In this example Derived
is standard-layout because Base
has no non-static data members:
struct Base
{
void Foo() {}
};
// std::is_standard_layout<<Derived> == true
struct Derived : public Base
{
int x;
int y;
};
Derived would also be standard-layout if Base
had the data members and Derived
had only member functions.
POD types
When a class or struct is both trivial and standard-layout, it is a POD (Plain Old Data) type. The memory layout of POD types is therefore contiguous and each member has a higher address than the member that was declared before it, so that byte for byte copies and binary I/O can be performed on these types. Scalar types such as int are also POD types. POD types that are classes can have only POD types as non-static data members.
Example
The following example shows the distinctions between trivial, standard-layout, and POD types:
#include <type_traits>
#include <iostream>
using namespace std;
struct B
{
protected:
virtual void Foo() {}
};
// Neither trivial nor standard-layout
struct A : B
{
int a;
int b;
void Foo() override {} // Virtual function
};
// Trivial but not standard-layout
struct C
{
int a;
private:
int b; // Different access control
};
// Standard-layout but not trivial
struct D
{
int a;
int b;
D() {} //User-defined constructor
};
struct POD
{
int a;
int b;
};
int main()
{
cout << boolalpha;
cout << "A is trivial is " << is_trivial<A>() << endl; // false
cout << "A is standard-layout is " << is_standard_layout<A>() << endl; // false
cout << "C is trivial is " << is_trivial<C>() << endl; // true
cout << "C is standard-layout is " << is_standard_layout<C>() << endl; // false
cout << "D is trivial is " << is_trivial<D>() << endl; // false
cout << "D is standard-layout is " << is_standard_layout<D>() << endl; // true
cout << "POD is trivial is " << is_trivial<POD>() << endl; // true
cout << "POD is standard-layout is " << is_standard_layout<POD>() << endl; // true
return 0;
}