When you start learning C++, many sources will tell you to always explicitly initialize member attributes of your classes. But why?
There are two things at play — when a user-defined constructor is invoked, the insides of the class are half initialized/half raw memory. Attributes that are trivial types (int, float) have no default constructor to invoke and thus their values will be unspecified (unless you explicitly initialize them).
#include <print>
struct S {
int i;
S() {}
};
struct S2 {
int i;
};
struct S3 {
int i;
S3() = default;
};
int main() {
std::println("I {}", S().i); // I 32637
// Following two are okay, because the constructor
// is not user-provided, it is compiler-generated.
// Thus, even trivial types are default-initialized.
std::println("I {}", S2().i); // I 0
std::println("I {}", S3().i); // I 0
}
Objects will have their default constructors called, but in the order they were declared in the class definition. If you are not careful, you can invoke a method on an object where the constructor hasn’t been invoked yet.
#include <print>
struct Logger {
Logger() {
std::println("Logger::ctor");
}
int get() const {
std::println("Logger::get");
return 42;
}
};
struct S {
Logger l;
int i;
S() : i(l.get()) {}
};
struct S2 {
int i;
Logger l;
// warning: field 'l' is uninitialized when used here [-Wuninitialized]
S2() : i(l.get()) {}
};
int main() {
S();
// outputs:
// Logger::ctor
// Logger::get
S2();
// outputs:
// Logger::get
// Logger::ctor
}
Explicit initialization can be done in one of three ways — in the attribute declaraction, in the initializer list, or in the constructor body. In all cases, do mind the initialization order of variables.
#include <print>
struct Logger {
Logger(int i) {
std::println("Logger::ctor({})", i);
}
};
struct S {
Logger l;
int i = 42;
// warning: field 'i' is uninitialized when used here [-Wuninitialized]
S() : l(i) {}
};
int main() {
S(); // outputs: Logger::ctor(779647075)
}