Constructors¶
A constructor is a special member function that initializes an object when it is created.
Key Characteristics¶
Same name as the class
No return type (not even
void)Automatically called when object is instantiated
Used to initialize object’s attributes
Can be overloaded (multiple constructors with different parameters)
class Vehicle {
public:
// Constructor
Vehicle(const std::string& color, const std::string& model, int max_speed) {
std::cout << "Vehicle constructed\n";
}
};
Vehicle car{"red", "sedan", 120}; // Constructor called automatically
Default Constructor¶
Definition: A constructor that takes no arguments.
Compiler-Generated Behavior¶
If you don’t define any constructor, the compiler generates a default constructor that:
Leaves built-in types (
int,double, etc) uninitialized (garbage values)Calls default constructor for user-defined type members (
std::string, etc.)Uses in-class initializers if provided
class Vehicle {
private:
std::string color_; // Default constructor → empty string
std::string model_; // Default constructor → empty string
bool is_running_{false}; // In-class initializer → false
int max_speed_; // UNINITIALIZED → garbage value!
};
Vehicle car; // Compiler-generated default constructor called
Danger
Uninitialized primitives contain garbage values! Always initialize or use in-class initializers.
User-Defined Default Constructor¶
class Vehicle {
private:
std::string color_;
std::string model_;
bool is_running_{false};
int max_speed_;
public:
// User-defined default constructor
Vehicle() {
color_ = "White";
model_ = "Unknown";
max_speed_ = 0;
std::cout << "Vehicle created with default values\n";
}
};
transportation::Vehicle car; // User-defined constructor called
Note
Once you define any constructor (even parameterized), the compiler will not generate a default constructor unless you explicitly request it with = default.
Parameterized Constructors¶
Definition: Constructors that accept one or more parameters to initialize attributes with specific values.
class Vehicle {
private:
std::string color_;
std::string model_;
int max_speed_;
public:
// Parameterized constructor
Vehicle(const std::string& color, const std::string& model, int max_speed) {
color_ = color;
model_ = model;
max_speed_ = max_speed;
}
// Another parameterized constructor (overloading)
Vehicle(int max_speed) {
color_ = "Gray";
model_ = "Standard";
max_speed_ = max_speed;
}
};
transportation::Vehicle car1{"red", "sedan", 120}; // Uses first constructor
transportation::Vehicle car2{150}; // Uses second constructor
Warning
The above approach uses assignment, not initialization. This is inefficient!
Member Initializer List¶
Definition: A more efficient way to initialize attributes directly before the constructor body executes.
Syntax¶
class Vehicle {
private:
std::string color_;
std::string model_;
bool is_running_;
int max_speed_;
public:
// Constructor with member initializer list
Vehicle(const std::string& color, const std::string& model, int max_speed)
: color_{color}, model_{model}, is_running_{false}, max_speed_{max_speed} {
// Body can be empty or contain additional logic
std::cout << "Vehicle constructed\n";
}
};
Why Use Member Initializer Lists?¶
- 1. Direct Initialization
Attributes are initialized directly, avoiding default construction + assignment.
- 2. Required for const and References
constand reference members must be initialized via initializer list.
class Vehicle {
private:
const std::string vin_; // const member
Engine& engine_; // reference member
public:
// MUST use initializer list for const and references
Vehicle(const std::string& vin, Engine& engine)
: vin_{vin}, engine_{engine} {
// vin_ = vin; // ❌ Error: cannot assign to const
// engine_ = engine; // ❌ Error: cannot reassign reference
}
};
- 3. Efficiency
Avoids creating temporary objects that occur with assignment.
Inefficient vs. Efficient Example¶
Inefficient (Assignment in Body):
class Vehicle {
private:
std::string model_;
public:
Vehicle(const std::string& model) {
model_ = model; // 1. Default construct, 2. Assign
}
};
Efficient (Member Initializer List):
class Vehicle {
private:
std::string model_;
public:
Vehicle(const std::string& model)
: model_{model} { // Direct construction
}
};
Detailed Example: Two-Step Problem¶
class A {
public:
A() { std::cout << "default ctor A\n"; }
A(int x) : x_{x} { std::cout << "param ctor A\n"; }
private:
int x_;
};
class B {
public:
// ❌ Inefficient: Two constructors called for A
B() {
std::cout << "default ctor B\n";
a_ = A(2); // Creates temporary, assigns, destroys
}
private:
A a_; // Default constructor called here first!
};
int main() {
B b;
}
Output:
default ctor A ← a_ default constructed
default ctor B ← B constructor body starts
param ctor A ← Temporary A(2) created
← Temporary assigned to a_, then destroyed
Fixed with Member Initializer List:
class B {
public:
// ✓ Efficient: Only one constructor called for A
B() : a_{2} {
std::cout << "default ctor B\n";
}
private:
A a_;
};
int main() {
B b;
}
Output:
param ctor A ← a_ constructed directly with A(2)
default ctor B ← B constructor body
C++ Object Construction Order¶
When an object is created, C++ follows a strict initialization sequence:
Base class constructors — If the class inherits from a base class, the base class constructor(s) execute first (in declaration order for multiple inheritance).
Member attribute constructors — All member objects are constructed in the order they are declared in the class definition, regardless of the order in the member initializer list.
Constructor body — Finally, the statements in the constructor body execute.
Important
The order in the member initializer list does not affect the initialization order. Members are always initialized in declaration order.
Constructor Initialization Order¶
Members are always initialized in declaration order, not the order in the initializer list.
class Vehicle {
private:
int max_speed_; // Initialized FIRST (declaration order)
std::string model_; // Initialized SECOND
public:
// Order in list doesn't matter!
Vehicle(const std::string& model, int speed)
: model_{model}, max_speed_{speed} { // List order ≠ init order
}
};
Tip
Match initializer list order to declaration order for readability, even though it doesn’t affect execution.
Declaration Order Matters¶
class Rectangle {
private:
double area_; // 1. initialized FIRST
double width_; // 2. initialized SECOND
double height_; // 3. initialized THIRD
public:
Rectangle(double w, double h)
: width_{w}, height_{h}, area_{width_ * height_} {
// ❌ PROBLEM: area_ is computed using uninitialized width_ and height_!
// area_ initializes FIRST, but width_ and height_ haven't been set yet
}
};
int main() {
Rectangle rect(4, 10); // area_ will have garbage value!
}
The Problem: Even though width_{w} and height_{h} appear before area_{width_ * height_} in the initializer list, area_ is initialized first (with uninitialized width_ and height_) because of declaration order.
Solution: Reorder the declarations:
class Rectangle {
private:
double width_; // 1. initialized FIRST
double height_; // 2. initialized SECOND
double area_; // 3. initialized THIRD
public:
Rectangle(double w, double h)
: width_{w}, height_{h}, area_{width_ * height_} {
// ✓ OK: width_ and height_ are initialized before area_
}
};
const Objects¶
Definition¶
A const object is an instance of a class whose state cannot be modified after it is created.
const Vehicle car("White", "Camry", 113);
car.start_engine(); // ❌ Error: cannot call non-const method on const object
Key Rules¶
When you declare an object as const, all its non-const methods become inaccessible—only methods marked as const can be called.
This enforces read-only access to the object’s internal data and ensures const-correctness.
class Vehicle {
private:
std::string model_;
bool is_running_{false};
public:
Vehicle(const std::string& model) : model_{model} {}
// Non-const method
void start_engine() {
is_running_ = true;
}
// Const method
[[nodiscard]] const std::string& get_model() const noexcept {
return model_;
}
[[nodiscard]] bool is_running() const noexcept {
return is_running_;
}
};
int main() {
const Vehicle car("Camry");
// ✓ OK: calling const methods
std::cout << car.get_model() << '\n';
std::cout << car.is_running() << '\n';
// ❌ Error: cannot call non-const method on const object
car.start_engine();
}
Note
Non-const objects can access both const and non-const methods, but const objects can only access const methods.
Constructor Best Practices¶
Important
Always use member initializer lists:
✓ More efficient (direct initialization)
✓ Required for
constand references✓ Prevents two-step initialization
✓ Industry standard practice
Template:
class Vehicle {
private:
std::string color_;
std::string model_;
int max_speed_;
bool is_running_{false}; // In-class initializer for defaults
public:
// Default constructor
Vehicle()
: color_{"White"}, model_{"Unknown"}, max_speed_{0} {
}
// Parameterized constructor
Vehicle(const std::string& color, const std::string& model, int max_speed)
: color_{color}, model_{model}, max_speed_{max_speed} {
// Validation logic can go here
if (max_speed_ < 0) {
throw std::invalid_argument("Speed cannot be negative");
}
}
};
Common Mistakes¶
Warning
Critical Errors:
❌ Not using member initializer lists
Vehicle(const std::string& model) {
model_ = model; // Inefficient! Use initializer list
}
❌ Forgetting semicolon after class
class Vehicle {
// ...
} // ❌ Missing semicolon!
❌ Uninitialized primitives
class Vehicle {
private:
int max_speed_; // ❌ Uninitialized! Use {0} or initializer list
};
❌ Wrong initialization order assumptions
class Vehicle {
private:
int speed_;
int max_speed_;
public:
// Members initialize in DECLARATION order (speed_, then max_speed_)
// Not in initializer list order!
Vehicle() : max_speed_{100}, speed_{max_speed_ / 2} {} // ❌ Dangerous!
};
Summary¶
Constructor Checklist:
✓ Always use member initializer lists
✓ Initialize all members (especially primitives)
✓ Validate parameters in constructor body when needed
✓ Provide both default and parameterized constructors when appropriate
✓ Use
const std::string&for string parameters✓ Match initializer list order to declaration order (for readability)
Tip
Quick rule: If you’re assigning to members in the constructor body instead of using an initializer list, you’re doing it wrong!
Example: Complete Constructor Implementation¶
namespace transportation {
class Vehicle {
private:
std::string color_;
std::string model_;
bool is_running_{false};
int max_speed_;
public:
// Default constructor
Vehicle()
: color_{"White"}, model_{"Unknown"}, max_speed_{0} {
}
// Parameterized constructor with validation
Vehicle(const std::string& color, const std::string& model, int max_speed)
: color_{color}, model_{model}, max_speed_{max_speed} {
if (max_speed_ < 0) {
throw std::invalid_argument("Max speed cannot be negative");
}
}
};
} // namespace transportation