Giao diện
⚡ Member Initialization — Khởi tạo thành viên
Member initialization list là cách hiệu quả và đúng đắn để khởi tạo members trong constructor. Đây là Modern C++ best practice.
Assignment vs Initialization List
❌ Assignment trong Constructor Body (Old way)
cpp
class OldStyle {
private:
std::string name_;
int value_;
public:
OldStyle(const std::string& name, int value) {
// Assignment — không phải initialization!
name_ = name; // 1. Default construct name_ (empty string)
// 2. Then assign new value (copy)
value_ = value; // 1. value_ uninitialized garbage
// 2. Then assign
}
};Vấn đề:
name_được construct 2 lần (default → assign)- Inefficient với complex objects
- Không hoạt động với const và reference members!
✅ Member Initialization List (Modern way)
cpp
class ModernStyle {
private:
std::string name_;
int value_;
public:
ModernStyle(const std::string& name, int value)
: name_(name), // Direct initialization — efficient!
value_(value) {} // Đã initialize, không cần body
};Cú pháp Member Initialization List
cpp
class Example {
private:
int a_;
double b_;
std::string c_;
public:
// Colon starts the initializer list
Example(int a, double b, const std::string& c)
: a_(a), // Initialize a_ with a
b_(b), // Initialize b_ with b
c_(c) // Initialize c_ with c
{
// Constructor body (often empty)
}
};Khi nào BẮT BUỘC dùng Initialization List?
1. const Members
cpp
class Config {
private:
const int maxSize_; // const — phải init trong list
public:
// ❌ Cannot assign to const in body
// Config(int size) { maxSize_ = size; } // Error!
// ✅ Must use initializer list
Config(int size) : maxSize_(size) {}
};2. Reference Members
cpp
class Logger {
private:
std::ostream& output_; // Reference — phải init trong list
public:
// ✅ Initialize reference in list
explicit Logger(std::ostream& out) : output_(out) {}
void log(const std::string& msg) {
output_ << "[LOG] " << msg << std::endl;
}
};
int main() {
Logger consoleLogger(std::cout);
consoleLogger.log("Hello!"); // [LOG] Hello!
}3. Members không có default constructor
cpp
class NoDefault {
public:
NoDefault(int x) {} // Không có default constructor
};
class Container {
private:
NoDefault obj_; // Không thể default construct!
public:
// ✅ Phải init trong list
Container(int val) : obj_(val) {}
// ❌ Error: NoDefault không có default constructor
// Container(int val) { obj_ = NoDefault(val); }
};4. Base Class Initialization
cpp
class Base {
public:
Base(int x) {} // Không có default constructor
};
class Derived : public Base {
private:
int value_;
public:
// ✅ Base class phải init trước members
Derived(int x, int v)
: Base(x), // Call base constructor
value_(v) {}
};Thứ tự Initialization
⚠️ QUAN TRỌNG
Members được initialize theo thứ tự khai báo, không phải thứ tự trong init list!
cpp
class Order {
private:
int first_; // 1st
int second_; // 2nd
int third_; // 3rd
public:
// ⚠️ Thứ tự trong list KHÔNG ảnh hưởng thứ tự thực tế
Order(int val)
: third_(val), // Nhưng third_ init cuối cùng!
first_(val), // Nhưng first_ init đầu tiên!
second_(val) {} // second_ init ở giữa
};Best Practice: Viết init list theo thứ tự khai báo members.
Default Member Initializers (C++11)
cpp
class ModernClass {
private:
int count_ = 0; // Default value
std::string name_ = "Unknown";
bool active_ = false;
std::vector<int> data_ = {1, 2, 3};
public:
// Default constructor uses all defaults
ModernClass() = default;
// Override some
explicit ModernClass(const std::string& name)
: name_(name) {} // count_, active_, data_ use defaults
// Override all
ModernClass(const std::string& name, int count)
: name_(name), count_(count) {} // active_, data_ use defaults
};Ưu điểm:
- ✅ Không quên initialize
- ✅ Defaults visible ở declaration
- ✅ Fewer constructor variations needed
Delegating Constructors (C++11)
cpp
class Rectangle {
private:
double width_;
double height_;
std::string color_;
public:
// Primary constructor
Rectangle(double w, double h, const std::string& color)
: width_(w), height_(h), color_(color) {}
// Delegate to primary — default color
Rectangle(double w, double h)
: Rectangle(w, h, "white") {}
// Delegate — square with default color
explicit Rectangle(double side)
: Rectangle(side, side) {} // Delegates to (double, double)
// Default — unit square
Rectangle()
: Rectangle(1.0) {} // Delegates to (double)
};Performance: Init List vs Body Assignment
cpp
#include <iostream>
#include <string>
class Heavy {
public:
Heavy() { std::cout << "Default construct\n"; }
Heavy(const std::string& s) { std::cout << "Parameterized: " << s << "\n"; }
Heavy& operator=(const Heavy& other) { std::cout << "Copy assign\n"; return *this; }
};
// ❌ Inefficient
class BadHolder {
Heavy h_;
public:
BadHolder(const std::string& s) {
h_ = Heavy(s); // Default construct + copy assign
}
};
// ✅ Efficient
class GoodHolder {
Heavy h_;
public:
GoodHolder(const std::string& s) : h_(s) {} // Direct construct
};
int main() {
std::cout << "=== Bad ===" << std::endl;
BadHolder bad("test");
// Output:
// Default construct
// Parameterized: test
// Copy assign
std::cout << "\n=== Good ===" << std::endl;
GoodHolder good("test");
// Output:
// Parameterized: test
}📚 Tổng kết
| Pattern | When to Use |
|---|---|
Init list : member_(val) | Luôn luôn (default) |
Default member init int x_ = 0; | Khi có sensible default |
| Delegating constructor | Avoid code duplication |
| Body assignment | Hầu như không bao giờ |
Rules:
- ✅ Luôn dùng initialization list
- ✅ const/ref members bắt buộc init list
- ✅ Viết init list theo thứ tự khai báo
- ✅ Dùng default member init cho defaults
➡️ Tiếp theo
Tiếp theo: RPG Character Project — Thực hành xây dựng class hoàn chỉnh.
🧠 Quiz
Câu 1: Khi nào BẮT BUỘC phải dùng member initializer list thay vì assignment trong constructor body?
- [ ] A) Khi member là kiểu
inthoặcdouble - [ ] B) Khi class có nhiều hơn 3 members
- [x] C) Khi member là
const, reference, hoặc class không có default constructor - [ ] D) Chỉ khi dùng C++17 trở lên
💡 Giải thích:
constmembers và reference members phải được khởi tạo ngay — không thể gán sau. Tương tự, nếu member là object của class không có default constructor, bạn phải dùng initializer list để gọi constructor phù hợp. Đây là quy tắc của ngôn ngữ, không phải convention.
Câu 2: Đoạn code sau có vấn đề gì?
cpp
class Widget {
int a_;
int b_;
public:
Widget(int val) : b_(val), a_(b_) {}
};- [ ] A) Không có vấn đề gì,
a_=b_=val - [x] B)
a_được khởi tạo trướcb_(theo thứ tự khai báo), nêna_nhận giá trị rác - [ ] C) Compile error vì dùng
b_trước khi khai báo - [ ] D) Runtime error do stack overflow
💡 Giải thích: Members được khởi tạo theo thứ tự khai báo trong class, không phải thứ tự trong initializer list.
a_được khai báo trướcb_, nêna_khởi tạo trước. Lúc đób_chưa được init →a_nhận giá trị rác. Luôn viết initializer list theo đúng thứ tự khai báo!
Câu 3: So với assignment trong constructor body, member initializer list có ưu điểm gì?
- [x] A) Hiệu quả hơn vì tránh default construction rồi assignment cho complex types
- [ ] B) Code ngắn hơn nên compile nhanh hơn
- [ ] C) Cho phép khởi tạo static members
- [ ] D) Tự động thêm
constcho tất cả members
💡 Giải thích: Với initializer list, member được direct-initialized một lần. Với assignment trong body, complex objects (như
std::string) phải default construct trước rồi mới assign — tốn gấp đôi chi phí. Với primitive types thì không khác biệt, nhưng best practice là luôn dùng initializer list.