You get a bonus - 1 coin for daily activity. Now you have 1 coin

Plain Data Structure (Passive Data Structure)

Lecture



Plain data structure, passive data structure- passive data structure (PDS, also termed a plain old data structure, or plain old data, POD),— in modern high-level programming languages, a data type that has a rigidly defined layout of fields in memory, requires no access restriction and no automatic management. Variables of this type can be copied with simple memory-block copying procedures such as memcpy. It is a data structure represented only as a passive collection of field values (instance variables) without using object-oriented features. Its opposite is a managed data structure.

The easiest way to define a plain data structure is by contradiction. If the compiler silently rearranges the fields without the user's knowledge, or silently calls a constructor when the data structure is created, or calls a destructor when it is destroyed, or a special copy procedure when it is copied, then this is a managed (that is, not plain) structure.

Advantages of plain data structures

Passive data structures are suitable when there is a part of the system where it must be clearly indicated that the detailed logic for manipulating the data and its integrity resides elsewhere. PDS are often found at the boundaries of a system, where information moves to or from other systems or to persistent storage, and the domain logic found in other parts of the system is irrelevant. For example, a PDS would be convenient for representing the field values of objects that are built from external data, in a part of the system where the semantic checks and interpretations required for valid objects have not yet been applied.

Plain data structures have two distinguishing features.

Predictable layout in memory

The compiler can automatically rearrange a data structure at its own discretion (for example, change the order of fields. In C++ this is possible only if there is an access label public/private/protected between the fields. A sequence of fields not separated by such a label must be laid out in memory in the order the fields were declared). Such a rearrangement can significantly save memory, but it breaks compatibility. In POD's this optimization is disabled.

In other words: types marked as POD are laid out in memory exactly as the programmer described (possibly with some alignment). Therefore only POD's can be used for communication between two runtime libraries. In particular — for passing data from program to program, from plugin to plugin, for communicating with code written in another programming language. To quickly write a complex file header such as a BMP header to disk, you can build it in memory and then write it out with a single command — but the data structure in which we build the header must also be a POD.

Absence of management code

This means that when an object is created there is no need to call a constructor, when it is copied — an assignment operation, and when it is destroyed — a destructor. This, in turn, gives the following advantages:

  1. Static initialization. Instead of silently calling a constructor at program startup without the programmer's knowledge, POD's can be assembled already at program compile time.
  2. Trivial copying (including copying of arrays) with functions such as memcpy.
  3. Again, this is important for communication between programs: after all, the memory manager should not be managing memory that does not belong to it.
  4. Only plain types can be members of a union (in Pascal, respectively, record/case).
  5. Functions with side effects (such as system functions that affect the result of a subsequent GetLastError call) do not combine well with automatically managed types.

Languages in which all types are plain

  • Standard Pascal
  • C

In C++

In C++, POD is defined by contradiction. A data type is a POD if:

  • it has no constructor, destructor, or copy assignment operator (that is, operator=, taking as input the same type);
  • among the non-static fields there are no C++ references, non-PODs, private or protected fields;
  • there are no virtual methods;
  • all its base classes, if any, are also PODs.

According to the C++ standard, a plain data type is laid out in memory exactly as described (and is fully byte-for-byte compatible in memory layout with a C structure). A managed structure, however, can be reorganized by the compiler however it deems most efficient.

Definition of POD before C++11:

An aggregate is either an array or a class that does not have:

  • private or protected non-static fields
  • it itself and all its bases along the entire inheritance chain do not have explicitly written constructors
  • virtual base classes, or base classes that are private or protected
  • virtual methods.

An aggregate can be initialized (as in C) with a list of the form = {1, 2, 3};

A scalar is:

  • a number
  • a pointer
  • a "pointer to member" (ptom, in the syntax C::* ptom)
  • enum
  • std::nullptr_t

(that is, a type that is not a class, an array, or a reference)

A POD is either a scalar, or an array of other PODs, or a class that is an aggregate, and in addition:

  • all non-static fields are PODs
  • there are no reference fields
  • there is no explicitly written operator=()
  • there is no explicitly written destructor

In C++11

«Predictable layout in memory» and «absence of management code» — are similar but different properties of a type. For example, the STRRET data structure, which in Windows is used to pass strings from one memory manager to another, can be «wrapped» in management code, but the second property — predictable layout — remains. Therefore the concept of PODs in C++11 is split into three.

A class is called "having a trivial copy constructor" if all of the following are true:

  • the copy constructor is auto-generated (NOT explicitly written)
  • the class has no virtual methods
  • the class has no virtual base classes
  • all base classes, as well as all fields of class types, also have a trivial copy constructor.

An auto-generated trivial copy constructor is memmove().

The concepts of "having a trivial default constructor/assignment operator/move constructor/move operator" are defined in exactly the same way.

A class is called "having a trivial destructor" if all of the following are true:

  • the destructor is auto-generated (NOT explicitly written)
  • the destructor is not virtual (throughout the entire inheritance chain, of course)
  • all base classes, as well as all fields of class types, also have a trivial destructor.

Such a class requires no destruction, and the memory holding it can be freed without cleanup.

A class is called "trivially copyable" if all of the special member functions listed above are trivial for it (except the default constructor, which may be non-trivial). Scalars, as well as arrays of trivially copyable objects, are also trivially copyable. Such types can be copied via memcpy.

A class is called "trivial" if it is trivially copyable and, in addition, its default constructor is trivial.

In other words, a class is trivial if the following are trivial for it:

  • the default constructor T();
  • the copy constructor T(T&);
  • the move constructor T(T&&);
    • It follows from this that a trivial class has no virtual methods and no virtual inheritance.
  • the destructor ~T();
  • the assignment operation operator=(T&);
  • the move operation operator=(T&&).
  • the types of all fields are trivial
  • all base classes, if any, are also trivial

A class is a standard-layout type if:

  • all non-static fields have the same access level (all private, all protected, or all public).
  • there are no virtual methods, no virtual inheritance.
  • there are no non-static reference fields
  • all non-static fields and base classes are also "standard-layout types".
  • it either has no base classes with non-static fields, or has no non-static fields of its own, while having no more than one base class with non-static fields (in other words, all non-static fields are declared in a single class across the entire inheritance hierarchy).
  • it has no base classes of the same type as the first non-static field.

Let us explain the last condition: in the language, there cannot be two different objects of the same type with the same address, from which it follows that the size of an empty class (with no non-static fields) cannot be 0 (it is at least 1). However, an exception is made for "part B in the class class D : B", and its size (if it is empty) can be exactly zero, which results in there being no "padding" between the start of D and its first field. But if the type of the first field is also B, the exception cannot apply, because (B*)&d and &(d.field1) point to different objects of the same type, and therefore "padding" is needed. The last condition in the list above means nothing more than "in standard-layout classes such padding is forbidden".

Such types have a predictable layout in memory (for example, the address of the object as a whole coincides with the address of its first field, naturally after a reinterpret_cast to the same type, for example void*), and they can be passed to another runtime library and to other programming languages.

Then a POD — is an array of other PODs, or a scalar, or a trivial standard-layout class all of whose non-static fields are also PODs.

For working with constants computed at compile time, and static initialization, C++11 has a softer notion — a literal type. Namely:

  • either a trivial default constructor T(), or some constructor (other than the copy and move constructors) marked as constexpr;
  • the copy constructor T(T&) is trivial;
    • it follows from this that a literal class has no virtual methods and no virtual inheritance.
  • the move constructor T(T&&) is trivial or deleted;
  • the destructor ~T() is trivial;
  • it is inherited from literal types;
  • all its non-static fields are literal.

PODs and "default" and "value" initialization

Starting with C++03, there is a difference between the statements T t; and T t();, as well as between new T and new T().

The version with empty parentheses is called "value initialization", and the one without them is called "default initialization".

Default initialization: if the default constructor is trivial, then nothing is done, and the object contains garbage. If the default constructor is non-trivial, it is executed.

Value initialization: if there is an explicitly written default constructor, it is executed. If there is not (that is, if the default constructor is trivial or is generated automatically), then the object is first zeroed out, and only then is the constructor executed (if it is non-trivial). Scalar types are zeroed out during value initialization.

In Embarcadero Delphi

All types are considered plain data structures except for:

  • the new unbounded-length strings (AnsiString, WideString, UnicodeString). However, if you do not touch the hidden management fields and work only with the data, System.Copy can be used — not forgetting, of course, that several strings may reference the same memory and that the UniqueString function must be called first;
  • COM interfaces;
  • dynamic arrays;
  • types that contain one of the three mentioned above;
  • new objects of type class. However, TObject, TButton, etc. — are pointers to an object and are always plain types!

In Java

In Java, some developers believe that the PDS concept corresponds to a class with public data members and no methods (Java Code Conventions 10.1), , i.e. a data transfer object. Others also include plain old Java objects (POJO), a class that has methods, but only getter and setter methods, without logic, and JavaBeans fall under the PDS concept if they do not use event handling and do not implement any methods beyond getters and setters. However, POJOs and Java Beans have encapsulation and therefore violate the fundamental definition of a PDS.

In other languages

In PHP, associative arrays and stdClass objects can be regarded as PDS.

Other representations of structured data, such as XML or JSON, can also be used as PDS, provided no significant semantic constraints are used.

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Structures and data processing algorithms."

Terms: Structures and data processing algorithms.