rspec/rules/S2107/cfamily/rule.adoc

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[]