The member initializer list for a class constructor allows members to be initialized to specified values and for base class constructors to be called with specific arguments. However, the order in which initialization occurs is fixed and does not depend on the order written in the member initializer list. The C++ Standard, [class.base.init], paragraph 11 [ISO/IEC 14882-2014], states the following:

In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class, virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
— Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
— Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
— Finally, the compound-statement of the constructor body is executed.
[Note: The declaration order is mandated to ensure that base and member subobjects are destroyed in the reverse order of initialization. —end note]

Consequently, the order in which member initializers appear in the member initializer list is irrelevant. The order in which members are initialized, including base class initialization, is determined by the declaration order of the class member variables or the base class specifier list. Writing member initializers other than in canonical order can result in undefined behavior, such as reading uninitialized memory.

Always write member initializers in a constructor in the canonical order: first, direct base classes in the order in which they appear in the base-specifier-list for the class, then nonstatic data members in the order in which they are declared in the class definition.

Noncompliant Code Example

In this noncompliant code example, the member initializer list for C::C() attempts to initialize someVal first and then to initialize dependsOnSomeVal to a value dependent on someVal. Because the declaration order of the member variables does not match the member initializer order, attempting to read the value of someVal results in an unspecified value being stored into dependsOnSomeVal.

class C {
  int dependsOnSomeVal;
  int someVal;
 
public:
  C(int val) : someVal(val), dependsOnSomeVal(someVal + 1) {}
};

Compliant Solution

This compliant solution changes the declaration order of the class member variables so that the dependency can be ordered properly in the constructor's member initializer list.

class C {
  int someVal;
  int dependsOnSomeVal;
 
public:
  C(int val) : someVal(val), dependsOnSomeVal(someVal + 1) {}
};

It is reasonable for initializers to depend on previously initialized values.

Noncompliant Code Example

In this noncompliant code example, the derived class, D, attempts to initialize the base class, B1, with a value obtained from the base class, B2. However, because B1 is initialized before B2 due to the declaration order in the base class specifier list, the resulting behavior is undefined.

class B1 {
  int val;
 
public:
  B1(int val) : val(val) {}
};

class B2 {
  int otherVal;
 
public:
  B2(int otherVal) : otherVal(otherVal) {}
  int get_other_val() const { return otherVal; }
};

class D : B1, B2 {
public:
  D(int a) : B2(a), B1(get_other_val()) {}
};

Compliant Solution

This compliant solution initializes both base classes using the same value from the constructor's parameter list instead of relying on the initialization order of the base classes.

class B1 {
  int val;
 
public:
  B1(int val) : val(val) {}
};

class B2 {
  int otherVal;
 
public:
  B2(int otherVal) : otherVal(otherVal) {}
};

class D : B1, B2 {
public:
  D(int a) : B1(a), B2(a) {}
};

Exceptions

OOP53-CPP-EX0: Constructors that do not use member initializers do not violate this rule.

Risk Assessment

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

OOP53-CPP

Medium

Unlikely

Medium

P4

L3

Automated Detection

Tool

Version

Checker

Description

Astrée

22.10

initializer-list-order
Fully checked
Axivion Bauhaus Suite

7.2.0

CertC++-OOP53
Clang
3.9
-Wreorder
CodeSonar
8.1p0

LANG.STRUCT.INIT.OOMI

Out of Order Member Initializers

Helix QAC

2024.1

C++4053
Klocwork
2024.1
CERT.OOP.CTOR.INIT_ORDER
LDRA tool suite
9.7.1

 

206 S

Fully implemented

Parasoft C/C++test
2023.1

CERT_CPP-OOP53-a

List members in an initialization list in the order in which they are declared
Polyspace Bug Finder

R2023b

CERT C++: OOP53-CPPChecks for members not initialized in canonical order (rule fully covered)
RuleChecker
22.10
initializer-list-order
Fully checked
SonarQube C/C++ Plugin
4.10
S3229

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Bibliography

[ISO/IEC 14882-2014]Subclause 12.6.2, "Initializing Bases and Members"
[Lockheed Martin 2005]AV Rule 75, Members of the initialization list shall be listed in the order in which they are declared in the class



7 Comments

  1. I propose that we add a new compliant solution for the first example, where we will have the initializer line read as this:

    C(int val) : SomeVal(val), DependsOnSomeVal(val + 1) {}

    This would be analogous to the second example's solution. Furthermore we might need a recommendation that the object constructor should not depend on the initialization order, if possible. The first compliant solution would violate that recommendation.

    1. I'm not certain what we would gain by doing that. One of the purposes to the first CS is to highlight that depending on previously-initialized members is compliant. There's no security risk when the initialization order is correct, and there are times when relying on a previously-initialized member has better performance or code clarity.

      I would not be opposed to a recommendation to not rely on initialization order when possible, however.

      1. I agree, adding my proposal for a new CS would not contribute to the issue. Forget about that.

        If that mentioned recommendation is written, then this rule would reference to that recommendation. That would be satisfactory for me.

  2. OOP53-EX1: Constructors that do not use member initializers do not violate this rule.

    I think this is redundant. The last sentence in the normative wording could be clarified, but I think is sufficient:

    Always write member initializers in a constructor in the canonical order

    If you haven't written a member initializer, there's no member initializer to write in the canonical order. I'd rather see that sentence clarified to not be so subtle than add an exception. But if you do keep that exception, I think it should be OOP53-CPP-EX1.

    1. Hi, Aaron!
      While I agree that we could clarify the normative wording, I still think the exception is worthwhile. I'm a fan of adding explicit exceptions, even if the exception is specified in the introduction. We consider redundancy in the name of clarity to be a Good Thing.
      But I did change the exception name as you suggest :)

      1. FWIW, as someone who has implemented checks for more than a few of these rules, exceptions are not really a Good Thing compared to clear normative guidance. I view them more of a last resort for one-off situations that don't fit the normative mold. Classes without member initializers aren't really a one-off, they're pretty common.

        1. Well, keep in mind that there are two audiences for these guidelines. One audience is the static analysis community, who need to build checkers...that includes you. The other audience are programmers who must comply with the rule but don't need to sweat the details. The clarity of the normative wording is for the SA checker community, and the exceptions serve the programmers. And I agree that constructors that lack member initializers are overwhelmingly common, but in the context of this rule, they are an edge case, as this rule is about the ordering of member initializers, and having none is trivial.