Skip to content

Introduction

Good practices are particularly important when programming in C and C++, as they improve code quality and robustness.

Designated Initializers

Designated initializers, which have been available in C99 for over two decades, were introduced in C++20. They allow you to initialize struct or aggregate members by name instead of by position. The basic syntax is:

struct PeriodicTaskInfo {
  std::chrono::milliseconds _computationTime;
  std::chrono::milliseconds _period;
  zpp_lib::PreemptableThreadPriority _priority;
  const char* _szTaskName;
};

// without designated initializers
PeriodicTaskInfo taskInfo1 = { 10ms, 50ms, zpp_lib::PreemptableThreadPriority::PriorityHigh, "Engine" };

// with designated initializers
PeriodicTaskInfo taskInfo2 = {
  ._computationTime = 10ms,
  ._period     = 50ms,
  ._priority   = zpp_lib::PreemptableThreadPriority::PriorityHigh,
  ._szTaskName = "Engine" };

The arguments for using designated initializers are:

  • Improved readability: The initializer is self-documenting. With positional initialization alone, the reader must look up the struct definition to understand the meaning of each value. This significantly improves code review.
  • Resilience to struct evolution: Adding a field to a struct modifies the memory layout. If one uses positional initializers only, this may assign completely incorrect values to fields. The same applies when a field is moved in the struct declaration:
    // Modified TaskInfo structure where the two first fields are swapped
    // All positional initializations will compile without error but with wrong values
    struct PeriodicTaskInfo {
      std::chrono::milliseconds _period;
      std::chrono::milliseconds _computationTime;
      zpp_lib::PreemptableThreadPriority _priority;
      const char* _szTaskName;
    };
    
  • Partial initialization with explicit defaults is possible with designated initializers. Additionally, struct instances can be initialized with fields that take their default values:

    struct PeriodicTaskInfo {
      std::chrono::milliseconds _computationTime;
      std::chrono::milliseconds _period;
      zpp_lib::PreemptableThreadPriority _priority;
      const char* _szTaskName;
      bool _enabled = true;
    };
    PeriodicTaskInfo taskInfo1 = {
      ._computationTime = 10ms,
      ._period     = 50ms,
      ._priority   = zpp_lib::PreemptableThreadPriority::PriorityHigh,
      ._szTaskName = "Engine" };
    };
    PeriodicTaskInfo taskInfo2 = {
      ._computationTime = 10ms,
      ._period     = 50ms,
      ._priority   = zpp_lib::PreemptableThreadPriority::PriorityLow,
      ._szTaskName = "DisabledTask",
      ._enabled    = false };
    }
    

  • Consistency between C and C++. C99 designated initializers have been the standard for over 25 years and are widely used in embedded codebases.