Share via


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

See Also

Basic Concepts