307 lines
6.9 KiB
Plaintext
307 lines
6.9 KiB
Plaintext
Partially initialized objects are surprising to the `class` users
|
|
and might lead to hard-to-catch bugs.
|
|
``class``es with constructors are expected to have all members initialized after their constructor finishes.
|
|
|
|
This rule raises an issue when some members are left uninitialized after a constructor finishes.
|
|
|
|
== Why is this an issue?
|
|
|
|
In the following example, `PartInit::x` is left uninitialized after the constructor finishes.
|
|
|
|
[source,cpp]
|
|
----
|
|
struct AutomaticallyInitialized {
|
|
int x;
|
|
AutomaticallyInitialized() : x(0) {}
|
|
};
|
|
|
|
struct PartInit {
|
|
AutomaticallyInitialized ai;
|
|
int x;
|
|
int y;
|
|
PartInit(int n) :y(n) {
|
|
// this->ai is initialized
|
|
// this->y is initialized
|
|
// Noncompliant: this->x is left uninitialized
|
|
}
|
|
};
|
|
----
|
|
|
|
This leads to undefined behavior in benign-looking code, like in the example below.
|
|
In this particular case, garbage value may be printed,
|
|
or a compiler may optimize away the print statement completely.
|
|
|
|
[source,cpp]
|
|
----
|
|
PartInit pi(1);
|
|
std::cout << pi.y; // Undefined behavior
|
|
----
|
|
|
|
For this reason, constructors should always initialize all data members of a class.
|
|
|
|
While in some cases, data members are initialized by their default constructor,
|
|
in others, they are left with garbage.
|
|
|
|
Types with a ``++default++``ed or implicit trivial default constructor follow the aggregate initialization syntax:
|
|
if you omit them from the initialization list, they will not be initialized.
|
|
|
|
[source,cpp]
|
|
----
|
|
struct Trivial {
|
|
int x;
|
|
int y;
|
|
};
|
|
struct Container {
|
|
Trivial t;
|
|
int arr[2];
|
|
Container() {
|
|
// Noncompliant
|
|
// this->t is not initialized
|
|
// this->t.x and this->t.y contain garbage
|
|
// this->arr contains garbage
|
|
}
|
|
Container(int) :t{}, arr{} {
|
|
// Compliant
|
|
// this->t.x and this->t.y are initialized to 0
|
|
// this->arr is initialized to {0, 0}
|
|
}
|
|
Container(int, int) :t{1}, arr{1} {
|
|
// Compliant
|
|
// this->t.x is 1
|
|
// this->t.y is 0
|
|
// this->arr is initialized to {1, 0}
|
|
}
|
|
};
|
|
struct DefaultedContainer {
|
|
Trivial t;
|
|
int arr[2];
|
|
DefaultedContainer() = default; // Noncompliant
|
|
// this->t and this->arr are not initialized
|
|
};
|
|
----
|
|
|
|
The same is true for a ``++default++``ed default constructor.
|
|
[source,cpp]
|
|
----
|
|
struct Defaulted {
|
|
int x;
|
|
Defaulted() = default;
|
|
};
|
|
struct ContainerDefaulted {
|
|
Defaulted d;
|
|
ContainerDefaulted() {
|
|
// Noncompliant this->d.x is not initialized
|
|
}
|
|
};
|
|
----
|
|
|
|
|
|
Even if some of the members have class initializers,
|
|
the other members are still not initialized by default.
|
|
|
|
[source,cpp]
|
|
----
|
|
struct Partial {
|
|
int x;
|
|
int y = 1;
|
|
int z;
|
|
};
|
|
struct ContainerPartial {
|
|
Partial p;
|
|
ContainrePartial() {
|
|
// Noncompliant
|
|
// this->p.x is not initialized
|
|
// this->p.y is initialized to 1
|
|
// this->p.z is not initialized
|
|
}
|
|
ContainrePartial(bool) :p{3} {
|
|
// Compliant
|
|
// this->p.x is initialized to 3
|
|
// this->p.y is initialized to 1
|
|
// this->p.z is initialized to 0
|
|
}
|
|
};
|
|
----
|
|
|
|
=== What is the potential impact?
|
|
|
|
It is a common expectation that an object is in a fully-initialized state after its construction.
|
|
A partially initialized object breaks this assumption.
|
|
|
|
This comes with all the risks associated with uninitialized variables,
|
|
and these risks propagate to all the classes using the faulty class as a type
|
|
as a base class or a data member.
|
|
This is all the more surprising
|
|
that most programmers expect a constructor to correctly initialize the members of its class.
|
|
|
|
include::../../../shared_content/cfamily/garbage_value_impact.adoc[]
|
|
|
|
=== Exceptions
|
|
|
|
Aggregate classes do not initialize most of their data members
|
|
(unless you explicitly value initialize them with `x{}` or `x()`)
|
|
but allow their users to use nice and flexible initialization syntax.
|
|
This rule ignores them.
|
|
|
|
== How to fix it
|
|
|
|
To avoid partially-initialized objects,
|
|
all non `+class+`-type fields should always be initialized (in order of preference):
|
|
|
|
* With an in-class initializer
|
|
* In the initialization list of a constructor
|
|
* In the constructor body
|
|
|
|
See rule S3230 for more details about this order.
|
|
|
|
If none of the above places is a good fit for a data member,
|
|
ask yourself whether it belongs to this class.
|
|
Instead of a data member, it might be more appropriate to keep the value
|
|
|
|
- as part of the class of a data member
|
|
(delegating initialization to the constructor of the member type),
|
|
- in a derived class (delegating initialization to the derived class),
|
|
- in a (potentially `static`) local variable of a member function, or
|
|
- in a parameter of a member function.
|
|
|
|
=== Code examples
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,cpp,diff-id=1,diff-type=noncompliant]
|
|
----
|
|
class C {
|
|
int val = 42;
|
|
};
|
|
|
|
class S {
|
|
public:
|
|
C c;
|
|
int i;
|
|
int j;
|
|
|
|
S() : i(0) {} // Noncompliant: this->j is left uninitialized
|
|
S(bool) : i(0), j(0) {}
|
|
};
|
|
|
|
class T {
|
|
public:
|
|
S s;
|
|
|
|
T() : s() {} // Noncompliant: s.j is left uninitialized
|
|
T(bool b) : s(b) {}
|
|
};
|
|
|
|
class U {
|
|
public:
|
|
T t;
|
|
|
|
U() : t() {} // Noncompliant: t.s.j is left uninitialized
|
|
};
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,cpp,diff-id=1,diff-type=compliant]
|
|
----
|
|
class C {
|
|
int val = 42;
|
|
};
|
|
|
|
class S_fixed {
|
|
public:
|
|
C c;
|
|
int i;
|
|
int j;
|
|
|
|
S_fixed() : i(0), j(0) {} // Compliant
|
|
S_fixed(bool) : i(0), j(0) {}
|
|
};
|
|
|
|
class T_fixed {
|
|
public:
|
|
S_fixed s;
|
|
|
|
T_fixed() : s() {} // Compliant
|
|
T_fixed(bool b) : s(b) {}
|
|
};
|
|
|
|
class U {
|
|
public:
|
|
T t;
|
|
|
|
U() : t() { t.s.j = 0; } // Compliant
|
|
};
|
|
----
|
|
|
|
==== Noncompliant code example
|
|
|
|
[source,cpp,diff-id=2,diff-type=noncompliant]
|
|
----
|
|
struct Partial {
|
|
int x;
|
|
int y = 1;
|
|
};
|
|
struct ContainerPartial {
|
|
Partial p;
|
|
ContainrePartial() {
|
|
// Noncompliant: this->p.x is not initialized
|
|
}
|
|
};
|
|
----
|
|
|
|
==== Compliant solution
|
|
|
|
[source,cpp,diff-id=2,diff-type=compliant]
|
|
----
|
|
struct Partial {
|
|
int x = 0;
|
|
int y = 1;
|
|
};
|
|
struct ContainerPartial {
|
|
Partial p;
|
|
ContainrePartial() {
|
|
// Compliant
|
|
}
|
|
};
|
|
----
|
|
|
|
== Resources
|
|
|
|
=== External coding guidelines
|
|
|
|
* {cpp} Core Guidelines - https://github.com/isocpp/CppCoreGuidelines/blob/e49158a/CppCoreGuidelines.md#c41-a-constructor-should-create-a-fully-initialized-object[C.41: A constructor should create a fully initialized object]
|
|
|
|
=== Related rules
|
|
|
|
* S836 detects the uses of uninitialized variables.
|
|
* S3230 describes the preferred place for initializing class data members.
|
|
|
|
=== Documentation
|
|
|
|
* {cpp} reference - https://en.cppreference.com/w/cpp/language/aggregate_initialization[Aggregate initialization]
|
|
* {cpp} reference - https://en.cppreference.com/w/cpp/language/direct_initialization[Direct initialization]
|
|
* {cpp} reference - https://en.cppreference.com/w/cpp/language/value_initialization[Value initialization]
|
|
|
|
|
|
ifdef::env-github,rspecator-view[]
|
|
|
|
'''
|
|
== Implementation Specification
|
|
(visible only on this page)
|
|
|
|
=== Message
|
|
|
|
* This field has no default and is not initialized in any constructors.
|
|
* This field has no default and is not initialized in n constructors.
|
|
|
|
|
|
'''
|
|
== Comments And Links
|
|
(visible only on this page)
|
|
|
|
=== relates to: S1258
|
|
|
|
|
|
endif::env-github,rspecator-view[]
|