Skip to content

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

PatternWhen to Use
Init list : member_(val)Luôn luôn (default)
Default member init int x_ = 0;Khi có sensible default
Delegating constructorAvoid code duplication
Body assignmentHầu như không bao giờ

Rules:

  1. ✅ Luôn dùng initialization list
  2. ✅ const/ref members bắt buộc init list
  3. ✅ Viết init list theo thứ tự khai báo
  4. ✅ 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 int hoặc double
  • [ ] 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: const members 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ước b_ (theo thứ tự khai báo), nên a_ 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ước b_, nên a_ 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 const cho 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.