Object Lifetime and Construction¶
Object construction, destruction, initialization forms, and copy elision. Understanding lifetime is essential for correctness and performance.
Key Facts¶
- Object lifetime: construction -> use -> destruction
- Construction order: base classes (left to right) -> members (declaration order) -> constructor body
- Destruction order: reverse of construction
- Member initializer list: preferred over assignment in body (constructs directly, not default+assign)
- Delegating constructors (C++11):
MyClass(int x) : MyClass(x, 0) {} explicitkeyword: prevents implicit single-argument constructor conversions- Copy elision (RVO/NRVO): compiler eliminates copy/move on return - mandatory in C++17
- Aggregate initialization:
Type{1, 2, 3}for structs/arrays without user constructors - Designated initializers (C++20):
Point{.x=1, .y=2} - Static local variables: initialized once, thread-safe in C++11
- Temporary lifetime extension: binding rvalue to
const T&extends lifetime to ref scope
Patterns¶
Constructor Forms¶
class Widget {
int id_;
std::string name_;
std::vector<int> data_;
public:
// Default constructor
Widget() : id_(0), name_("default") {}
// Parameterized - use initializer list
Widget(int id, std::string name)
: id_(id), name_(std::move(name)) {} // move string into member
// Delegating constructor
Widget(int id) : Widget(id, "unnamed") {}
// Explicit - prevent implicit conversion from int
explicit Widget(double d) : Widget(static_cast<int>(d)) {}
// In-class member initializers (C++11)
int priority_ = 0; // default if not in init list
bool active_ = true;
};
Initialization Syntax¶
// Direct initialization
int a(42);
std::string s("hello");
// Copy initialization
int b = 42;
std::string s2 = "hello";
// List initialization (C++11) - narrowing-safe
int c{42};
// int d{3.14}; // ERROR: narrowing from double to int
// Aggregate initialization
struct Point { double x, y, z; };
Point p1{1.0, 2.0, 3.0};
Point p2{.x = 1.0, .y = 2.0}; // C++20 designated initializers
// std::initializer_list
std::vector<int> v{1, 2, 3, 4, 5};
// Default initialization vs value initialization
int* a1 = new int; // indeterminate value (default init)
int* a2 = new int(); // zero (value init)
int* a3 = new int{}; // zero (value init via list init)
Rule of Zero / Five¶
// Rule of Zero: let standard types manage resources
struct Employee {
std::string name;
std::vector<std::string> skills;
std::unique_ptr<Address> address;
// No special member functions needed - compiler generates correct ones
};
// Rule of Five: when managing a raw resource
class RawBuffer {
size_t size_;
char* data_;
public:
explicit RawBuffer(size_t n) : size_(n), data_(new char[n]) {}
~RawBuffer() { delete[] data_; }
RawBuffer(const RawBuffer& other)
: size_(other.size_), data_(new char[other.size_]) {
std::memcpy(data_, other.data_, size_);
}
RawBuffer& operator=(const RawBuffer& other) {
if (this != &other) {
auto tmp = RawBuffer(other);
std::swap(size_, tmp.size_);
std::swap(data_, tmp.data_);
}
return *this;
}
RawBuffer(RawBuffer&& other) noexcept
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr;
}
RawBuffer& operator=(RawBuffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
};
Copy Elision / RVO¶
// Guaranteed copy elision (C++17) - no copy/move constructor needed
Widget create() {
return Widget(42, "test"); // constructed directly in caller
}
// Named Return Value Optimization (NRVO) - not guaranteed but common
Widget build() {
Widget w(1, "build");
w.configure();
return w; // usually elided, otherwise moved
}
// Passing temporary to function
void consume(Widget w);
consume(Widget(42, "tmp")); // elided: constructed directly in parameter
Static and Thread-Local¶
// Function-local static: initialized once, thread-safe (C++11)
Logger& get_logger() {
static Logger instance; // Meyers' Singleton
return instance;
}
// Thread-local storage
thread_local int tls_counter = 0;
void per_thread_work() {
++tls_counter; // each thread has its own copy
}
Gotchas¶
- Issue: Most vexing parse:
Widget w();declares a function, not an object -> Fix: UseWidget w{};orWidget w;for default construction - Issue: Brace initialization with
std::initializer_listoverload surprises:vector<int>{10}creates 1-element vector with value 10, not 10-element vector -> Fix: Usevector<int>(10)for count-based constructor,vector<int>{10}for single-element list - Issue: Member initializer list order doesn't match declaration order -> members initialized in declaration order regardless -> Fix: Always write init list in same order as member declarations. Enable
-Wreorder. - Issue: Temporary bound to
const T¶meter in constructor stored as member -> dangling after constructor -> Fix: Take by value and move, or take bystring_viewand copy into string member