Skip to content

🎭 Polymorphism & vtable: Đa Hình Dưới Nắp Ca-pô Module 2

Polymorphism — đa hình — là trụ cột quan trọng nhất của OOP. Nó cho phép bạn viết code xử lý chung, nhưng mỗi đối tượng tự biết cách hành xử riêng. Bài này đi sâu vào cơ chế virtual, bản chất của vtable, và những quy tắc bắt buộc khi dùng đa hình trong production.

🎯 Mục tiêu

Sau bài này, bạn sẽ:

  • Hiểu polymorphism là gì và tại sao nó quan trọng trong thiết kế phần mềm
  • Nắm cách virtual functions hoạt động — static dispatch vs dynamic dispatch
  • Vẽ được vtable diagram trong đầu khi đọc code đa hình
  • Sử dụng đúng override, final, và pure virtual functions
  • Biết tại sao virtual destructor là bắt buộc — và hậu quả nếu quên
  • Đánh giá được chi phí thực tế của dynamic dispatch

Tại Sao Cần Đa Hình?

Hãy tưởng tượng bạn xây dựng hệ thống thanh toán. Có nhiều phương thức: thẻ tín dụng, chuyển khoản, crypto. Mỗi loại xử lý khác nhau, nhưng flow chung giống nhau.

cpp
// ❌ CÁCH SAI — God function với switch/case
void processPayment(const std::string& type, double amount) {
    if (type == "credit_card") {
        std::cout << "Charged credit card: " << amount << " VND\n";
    } else if (type == "bank_transfer") {
        std::cout << "Bank transfer: " << amount << " VND\n";
    } else if (type == "crypto") {
        std::cout << "Crypto payment: " << amount << " BTC\n";
    }
    // Thêm phương thức mới? Sửa hàm này → vi phạm Open/Closed Principle
}
cpp
// ✅ CÁCH ĐÚNG — Polymorphism: gọi chung, xử lý riêng
void processPayment(std::unique_ptr<Payment> payment) {
    if (payment->validate()) {
        payment->process();        // Mỗi loại tự biết cách xử lý
        std::cout << payment->receipt() << "\n";
    }
}
// Thêm phương thức mới? Tạo class mới, KHÔNG sửa code cũ ✅

Đó chính là sức mạnh của đa hình: viết code một lần, hoạt động đúng với mọi kiểu con.


Virtual Functions — Chìa Khóa Đa Hình

Static Dispatch vs Dynamic Dispatch

C++ có hai cơ chế gọi hàm:

Đặc điểmStatic DispatchDynamic Dispatch
Quyết định lúc nàoCompile timeRuntime
Từ khóaKhông cần virtualCần virtual
Dựa vàoKiểu con trỏKiểu thực tế của object
Linh hoạtKhôngCó — đa hình

So sánh: Không virtual vs Có virtual

cpp
#include <iostream>

// ── KHÔNG virtual ──
class PaymentA {
public:
    void process() { std::cout << "PaymentA::process()\n"; }
};
class CreditCardA : public PaymentA {
public:
    void process() { std::cout << "CreditCardA::process()\n"; }
};

// ── CÓ virtual ──
class PaymentB {
public:
    virtual void process() { std::cout << "PaymentB::process()\n"; }
    virtual ~PaymentB() = default;
};
class CreditCardB : public PaymentB {
public:
    void process() override { std::cout << "CreditCardB::process()\n"; }
};

int main() {
    CreditCardA a;
    PaymentA* ptrA = &a;
    ptrA->process();   // PaymentA::process()    ❌ Static dispatch — nhìn kiểu con trỏ

    CreditCardB b;
    PaymentB* ptrB = &b;
    ptrB->process();   // CreditCardB::process() ✅ Dynamic dispatch — nhìn object thật
}
PaymentA::process()       ← SAI! ptrA trỏ đến CreditCardA mà
CreditCardB::process()    ← ĐÚNG! virtual → gọi đúng hàm của object thật

Quy tắc: Không virtual → compiler nhìn kiểu con trỏ. Có virtual → runtime nhìn object thực tế.

📌 Quy tắc vàng

Chỉ cần đặt virtual ở class base. Các class derived tự động kế thừa tính virtual. Nhưng luôn luôn thêm override ở derived class để compiler kiểm tra giúp bạn.


The vtable — Bản Đồ Tinh Thần

Đây là phần quan trọng nhất của bài. Hiểu vtable = hiểu polymorphism hoạt động thế nào "dưới nắp ca-pô".

vtable Là Gì?

Khi bạn khai báo một class có virtual functions, compiler tạo ra:

  1. vtable (virtual table) — một bảng chứa con trỏ đến các hàm virtual, mỗi class có MỘT vtable
  2. vptr (virtual pointer) — một con trỏ ẩn trong mỗi object, trỏ đến vtable của class tương ứng

ASCII Diagram — Object Layout

Giả sử ta có class hierarchy:

cpp
class Payment {
public:
    virtual bool process() { return false; }
    virtual std::string receipt() const { return "generic"; }
    virtual void cancel() { /* default cancel */ }
    virtual ~Payment() = default;
protected:
    double amount_ = 0.0;
};

class CreditCard : public Payment {
public:
    bool process() override { /* charge card */ return true; }
    std::string receipt() const override { return "CC receipt"; }
    // cancel() KHÔNG override → dùng Payment::cancel()
private:
    std::string card_number_;
};

class BankTransfer : public Payment {
public:
    bool process() override { /* transfer */ return true; }
    std::string receipt() const override { return "Bank receipt"; }
    // cancel() KHÔNG override → dùng Payment::cancel()
private:
    std::string bank_code_;
};

Đây là cách bộ nhớ thực sự trông như thế nào:

CreditCard object:
┌────────────────┐
│ vptr ─────────────→ vtable_CreditCard
│ (8 bytes)      │    ┌───────────────────────────────────┐
├────────────────┤    │ [0] process() → CreditCard::process│
│ amount_        │    │ [1] receipt() → CreditCard::receipt │
│ (8 bytes)      │    │ [2] cancel()  → Payment::cancel    │
├────────────────┤    │ [3] ~dtor()   → CreditCard::~dtor  │
│ card_number_   │    └───────────────────────────────────┘
│ (32 bytes*)    │    * Mọi CreditCard objects chia sẻ CÙNG vtable
└────────────────┘

BankTransfer object:
┌────────────────┐
│ vptr ─────────────→ vtable_BankTransfer
│ (8 bytes)      │    ┌─────────────────────────────────────┐
├────────────────┤    │ [0] process() → BankTransfer::process│
│ amount_        │    │ [1] receipt() → BankTransfer::receipt │
│ (8 bytes)      │    │ [2] cancel()  → Payment::cancel      │
├────────────────┤    │ [3] ~dtor()   → BankTransfer::~dtor  │
│ bank_code_     │    └─────────────────────────────────────┘
│ (32 bytes*)    │    * Mọi BankTransfer objects chia sẻ CÙNG vtable
└────────────────┘

* Kích thước std::string phụ thuộc implementation (thường 32 bytes với SSO)

Quá Trình Dynamic Dispatch — Từng Bước

Khi bạn viết:

cpp
Payment* p = new CreditCard();
p->process();   // Chuyện gì xảy ra?

Runtime thực hiện 3 bước:

Bước 1: Đọc vptr từ object
    p ──→ CreditCard object ──→ vptr

Bước 2: Tra vtable, tìm slot của process()
    vptr ──→ vtable_CreditCard[0] = &CreditCard::process

Bước 3: Gọi hàm qua con trỏ
    call CreditCard::process(this)

Tổng chi phí: 1 lần đọc bộ nhớ (vptr) + 1 lần đọc bảng (vtable slot)
             ≈ 1-2 nanoseconds trên CPU hiện đại

🧠 Điểm mấu chốt

  • Mỗi class với virtual functions có một vtable duy nhất (lưu trong vùng nhớ read-only)
  • Mỗi objectmột vptr ẩn (8 bytes trên 64-bit) — đây là "giá" bạn trả cho đa hình
  • Nếu derived class override một hàm → slot tương ứng trong vtable trỏ đến hàm mới
  • Nếu không override → slot giữ nguyên con trỏ đến hàm của base class

overridefinal — Lưới An Toàn Từ C++11

override — Bắt Lỗi Tại Compile Time

override nói với compiler: "Tôi đang override một virtual function ở base class."

cpp
class Payment {
public:
    virtual bool process() = 0;
    virtual std::string receipt() const = 0;
    virtual ~Payment() = default;
};

class CreditCard : public Payment {
public:
    // ✅ Đúng — override hàm virtual từ base
    bool process() override { return true; }

    // ❌ Compile error! Sai chữ ký — base có `const`, derived quên `const`
    // std::string receipt() override { return "CC"; }
    //                       ^^^^^^^^ lỗi vì signature không khớp

    // ✅ Đúng — khớp chính xác chữ ký
    std::string receipt() const override { return "CC receipt"; }
};

Không có override, lỗi chính tả tạo hàm MỚI thay vì override:

cpp
class BrokenCard : public Payment {
    // Quên `const` → tạo hàm mới, KHÔNG override
    std::string receipt() { return "broken"; }  // ← Compiler im lặng ❌
    // Khi gọi qua Payment*, vẫn gọi Payment::receipt() → BUG âm thầm
};

⚠️ QUY TẮC BẮT BUỘC

Luôn luôn dùng override khi override virtual functions. Không có ngoại lệ. Đây là lưới an toàn miễn phí — compiler bắt lỗi giúp bạn thay vì để runtime bugs lọt ra production.

final — Chặn Override Tiếp

final có hai dạng:

cpp
// Dạng 1: Chặn override MỘT hàm cụ thể
class Payment {
public:
    virtual bool validate() { return amount_ > 0; }
    virtual ~Payment() = default;
protected:
    double amount_ = 0.0;
};

class SecurePayment : public Payment {
public:
    // Không class con nào được override validate() nữa
    bool validate() final {
        return amount_ > 0 && amount_ < 1'000'000;
    }
};

class HackedPayment : public SecurePayment {
    // ❌ Compile error: validate() is final
    // bool validate() override { return true; }
};
cpp
// Dạng 2: Chặn KẾ THỪA toàn bộ class
class FinalPayment final : public Payment {
public:
    bool process() override { return true; }
};

// ❌ Compile error: cannot inherit from final class
// class EvilPayment : public FinalPayment {};

📌 Khi nào dùng final?

  • Hàm final: Khi logic đã hoàn chỉnh, override thêm sẽ gây bug (validation, security checks)
  • Class final: Khi class không được thiết kế để kế thừa (performance-critical, singleton)
  • Bonus: final có thể giúp compiler devirtualize — gọi trực tiếp thay vì qua vtable → nhanh hơn

Pure Virtual & Abstract Classes

Pure virtual function = hàm virtual không có implementation, derived class bắt buộc phải implement:

cpp
class IPaymentProcessor {
public:
    virtual bool process(double amount) = 0;        // = 0 → pure virtual
    virtual std::string receipt() const = 0;
    virtual ~IPaymentProcessor() = default;

    // CÓ THỂ có hàm non-pure (default implementation)
    virtual bool validate(double amount) {
        return amount > 0.0 && amount < 1'000'000.0;
    }
};

// ❌ IPaymentProcessor proc;  → Compile error: abstract class
// ✅ Chỉ dùng qua con trỏ/tham chiếu đến derived class

class VNPayProcessor : public IPaymentProcessor {
public:
    bool process(double amount) override {
        std::cout << "VNPay: " << amount << " VND\n";
        return true;
    }
    std::string receipt() const override {
        return "VNPay Receipt #VN-2024-001";
    }
    // validate() không override → dùng default từ base
};

🧠 Interface vs Abstract Class trong C++

C++ không có keyword interface như Java/C#. Thay vào đó:

  • Interface = class chỉ có pure virtual functions + virtual destructor (không data members)
  • Abstract class = class có mix pure virtual + implemented functions + data members
  • Convention: prefix I cho interfaces → IPaymentProcessor, ILogger, ISerializer

Virtual Destructor — Quy Tắc SỐNG CÒN

Câu hỏi phỏng vấn chắc chắn gặp. Hiểu sai = memory leak trong production.

cpp
class Resource {
public:
    Resource() { data_ = new int[1000]; }
    ~Resource() { delete[] data_; }         // ❌ KHÔNG virtual!
private:
    int* data_;
};

class BigResource : public Resource {
public:
    BigResource() { extra_ = new double[10000]; }  // 80KB
    ~BigResource() { delete[] extra_; }
private:
    double* extra_;
};

int main() {
    Resource* ptr = new BigResource();
    delete ptr;  // ⚠️ CHỈ gọi Resource::~Resource()
                 // BigResource::~BigResource() KHÔNG CHẠY → LEAKED 80KB!
}

Fix: Thêm virtual vào destructor base class:

cpp
class Resource {
public:
    Resource() { data_ = new int[1000]; }
    virtual ~Resource() { delete[] data_; }  // ✅ Virtual destructor
private:
    int* data_;
};

// Bây giờ: delete ptr → gọi BigResource::~BigResource() TRƯỚC
//                       → gọi Resource::~Resource() SAU → Không leak!

🚨 QUY TẮC SẮT ĐÁ — Không bao giờ được phá

Nếu class có BẤT KỲ virtual function nào → destructor PHẢI virtual.

cpp
class Base {
public:
    virtual void doSomething() = 0;
    virtual ~Base() = default;      // ← BẮT BUỘC
};

Lý do: Nếu class có virtual function, nó được thiết kế để dùng qua base pointer. Và nếu dùng qua base pointer, delete basePtr phải gọi đúng destructor.

Quên quy tắc này = undefined behavior + memory leak + resource leak.


Chi Phí Của Virtual Functions

Chi phíGiá trịGhi chú
Per-CLASSN × 8 bytes (vtable)Tạo 1 lần, shared bởi mọi object
Per-OBJECT8 bytes (vptr)Dù có 1 hay 100 virtual functions
Per-CALL~1-2 nanoseconds1 pointer indirection + ngăn inline

Khi Nào Chi Phí Quan Trọng?

cpp
// 🟢 KHÔNG quan trọng — 99% trường hợp
void processOrder(std::unique_ptr<Payment> payment) {
    payment->process();     // 1 virtual call → 2ns? Ai quan tâm
    payment->receipt();     // UI sẽ mất 16ms để render frame tiếp theo
}

// 🔴 CÓ THỂ quan trọng — hot inner loop
void renderParticles(std::vector<Particle*>& particles) {
    for (int frame = 0; frame < 60; ++frame) {           // 60 FPS
        for (auto* p : particles) {                       // 1 triệu particles
            p->update();    // 1M × 2ns = 2ms mỗi frame
            p->draw();      // 1M × 2ns = 2ms mỗi frame
        }
        // Tổng overhead: ~4ms/frame → budget chỉ có 16ms → 25% wasted!
    }
}

📌 Quy tắc thực tế

Đừng tránh polymorphism vì "tốc độ" nếu chưa profile. Chi phí virtual call (~2ns) so với network call (~1ms) = 500,000 lần nhỏ hơn. Chỉ tối ưu khi profiler chỉ ra bottleneck — thường chỉ trong game engines, HFT, hoặc embedded systems.

Alternatives khi cần: CRTP (static polymorphism), std::variant + std::visit, hoặc final để compiler devirtualize.


Business Example — Payment Processing System

Ví dụ production-grade kết hợp tất cả kiến thức:

cpp
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>

// ─── Interface ───
class Payment {
public:
    virtual ~Payment() = default;
    virtual bool validate() const = 0;
    virtual bool process() = 0;
    virtual std::string receipt() const = 0;

    double amount() const { return amount_; }

protected:
    explicit Payment(double amount) : amount_(amount) {
        if (amount <= 0.0) throw std::invalid_argument("Amount must be positive");
    }
    double amount_;
    bool processed_ = false;
};

// ─── CreditCard ───
class CreditCard : public Payment {
public:
    CreditCard(double amount, std::string card)
        : Payment(amount), card_(std::move(card)) {}

    bool validate() const override {
        return !card_.empty() && amount_ < 50'000'000.0;
    }
    bool process() override {
        if (!validate() || processed_) return false;
        processed_ = true;
        std::cout << "[CC] Charged " << amount_ << " VND\n";
        return true;
    }
    std::string receipt() const override {
        return "CC Receipt: " + std::to_string(amount_) + " VND";
    }
private:
    std::string card_;
};

// ─── BankTransfer ───
class BankTransfer : public Payment {
public:
    BankTransfer(double amount, std::string bank)
        : Payment(amount), bank_(std::move(bank)) {}

    bool validate() const override { return !bank_.empty(); }
    bool process() override {
        if (!validate() || processed_) return false;
        processed_ = true;
        std::cout << "[Bank] Transferred " << amount_ << " VND via " << bank_ << "\n";
        return true;
    }
    std::string receipt() const override {
        return "Bank Receipt: " + std::to_string(amount_) + " VND";
    }
private:
    std::string bank_;
};

// ─── CryptoPayment (final — không cho kế thừa tiếp) ───
class CryptoPayment final : public Payment {
public:
    CryptoPayment(double amount, std::string wallet)
        : Payment(amount), wallet_(std::move(wallet)) {}

    bool validate() const override { return wallet_.length() >= 26; }
    bool process() override {
        if (!validate() || processed_) return false;
        processed_ = true;
        std::cout << "[Crypto] Sent " << amount_ << " to " << wallet_.substr(0, 8) << "...\n";
        return true;
    }
    std::string receipt() const override {
        return "Crypto Receipt: " + std::to_string(amount_);
    }
private:
    std::string wallet_;
};

// ─── Đa hình: xử lý MỌI payment type bằng 1 hàm ───
void processOrder(Payment& payment) {
    if (!payment.validate()) { std::cout << "❌ Validation failed\n"; return; }
    if (payment.process())   { std::cout << "" << payment.receipt() << "\n"; }
    else                     { std::cout << "❌ Processing failed\n"; }
}

int main() {
    std::vector<std::unique_ptr<Payment>> payments;
    payments.push_back(std::make_unique<CreditCard>(500'000, "4111111111111111"));
    payments.push_back(std::make_unique<BankTransfer>(1'200'000, "VCB"));
    payments.push_back(std::make_unique<CryptoPayment>(0.05, "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"));

    for (auto& p : payments) { processOrder(*p); }
}
[CC] Charged 500000 VND
✅ CC Receipt: 500000.000000 VND
[Bank] Transferred 1200000 VND via VCB
✅ Bank Receipt: 1200000.000000 VND
[Crypto] Sent 0.05 to 1A1zP1eP...
✅ Crypto Receipt: 0.050000

Điểm mấu chốt: processOrder() không biết nó đang xử lý loại nào. Thêm phương thức mới? Tạo class mới, không sửa code cũ.


Bài Tập Nhanh — Dự Đoán Output

🏋️ Fast Exercise: Dự đoán hàm nào được gọi

Cho class hierarchy sau:

cpp
class Animal {
public:
    virtual void speak() { std::cout << "...\n"; }
    void eat() { std::cout << "Animal eats\n"; }       // KHÔNG virtual
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override { std::cout << "Woof!\n"; }
    void eat() { std::cout << "Dog eats bones\n"; }    // Hides, KHÔNG override
};

class Puppy : public Dog {
public:
    void speak() override { std::cout << "Yip!\n"; }
};

Dự đoán output:

cpp
Animal* a = new Puppy();
Dog* d = new Puppy();
Puppy* p = new Puppy();

a->speak();    // ???
a->eat();      // ???
d->speak();    // ???
d->eat();      // ???
p->speak();    // ???
p->eat();      // ???

delete a;
delete d;
delete p;
👉 Xem đáp án
a->speak();    // "Yip!"         — virtual → dynamic dispatch → Puppy::speak()
a->eat();      // "Animal eats"  — KHÔNG virtual → static dispatch → Animal::eat()
d->speak();    // "Yip!"         — virtual → dynamic dispatch → Puppy::speak()
d->eat();      // "Dog eats bones" — KHÔNG virtual → static dispatch → Dog::eat()
p->speak();    // "Yip!"         — virtual → dynamic dispatch → Puppy::speak()
p->eat();      // "Dog eats bones" — KHÔNG virtual → static dispatch → Dog::eat()

Quy luật:

  • virtual → nhìn object thực tế (Puppy) → dynamic dispatch
  • Không virtual → nhìn kiểu con trỏ (Animal*, Dog*, Puppy*) → static dispatch

🐛 Spot The Bug

🔍 Spot The Bug — Tìm lỗi trong code sau

cpp
class Logger {
public:
    virtual void log(const std::string& msg) {
        std::cout << "[LOG] " << msg << "\n";
    }
    ~Logger() = default;  // 🤔 Có gì sai không?
};

class FileLogger : public Logger {
public:
    FileLogger(const std::string& filename)
        : file_(std::fopen(filename.c_str(), "w")) {}

    void log(const std::string& msg) override {
        if (file_) std::fprintf(file_, "[FILE] %s\n", msg.c_str());
    }

    ~FileLogger() {
        if (file_) std::fclose(file_);
    }

private:
    FILE* file_;
};

void useLogger() {
    Logger* logger = new FileLogger("app.log");
    logger->log("Starting...");
    delete logger;   // 💥 Chuyện gì xảy ra?
}
👉 Xem phân tích bug

Bug: ~Logger() không phải virtual!

Khi delete logger:

  1. logger có kiểu Logger*
  2. Destructor không virtual → static dispatch → gọi Logger::~Logger()
  3. FileLogger::~FileLogger() KHÔNG được gọi
  4. file_ KHÔNG được fclose()file descriptor leak 🚨

Fix:

cpp
class Logger {
public:
    virtual void log(const std::string& msg) { ... }
    virtual ~Logger() = default;  // ✅ Virtual destructor!
};

Hậu quả thực tế: Trong server chạy 24/7, mỗi request tạo FileLogger → mỗi request leak 1 file descriptor → vài giờ sau: "Too many open files" → server crash.


🎬 Production Scenario — Khi Đa Hình Cứu Project

🎬 Scenario: Notification System

Tình huống: Team bạn xây notification system. Ban đầu chỉ có email. PM yêu cầu thêm SMS, Slack, Telegram…

❌ Không đa hình — sửa hàm cũ mỗi lần thêm kênh:

cpp
void sendNotification(const std::string& type, const std::string& msg) {
    if (type == "email") { sendEmail(msg); }
    else if (type == "sms") { sendSMS(msg); }
    // PM: "Thêm Telegram nhé!" → Dev: sửa hàm lần thứ 5...
}

✅ Đa hình — thêm class mới, không sửa code cũ:

cpp
class INotifier {
public:
    virtual ~INotifier() = default;
    virtual bool send(const std::string& message) = 0;
};

// Mỗi kênh = 1 class. Thêm kênh = thêm file, 0 sửa đổi code cũ.
void notifyAll(const std::vector<std::unique_ptr<INotifier>>& notifiers,
               const std::string& msg) {
    for (const auto& n : notifiers) { n->send(msg); }
}

Open/Closed Principle trong thực tế.


Những Sai Lầm Phổ Biến

Sai lầm 1: Quên override → tạo hàm mới thay vì override

cpp
class Base { virtual void process(int id); };
class Derived : public Base {
    void process(long id);          // ❌ Khác signature → hàm MỚI!
    void process(int id) override;  // ✅ Compiler kiểm tra giúp
};

Sai lầm 2: Lạm dụng dynamic_cast

cpp
// ❌ ANTI-PATTERN — if/else chain trá hình
void handle(Payment* p) {
    if (auto* cc = dynamic_cast<CreditCard*>(p)) { cc->chargeCard(); }
    else if (auto* bt = dynamic_cast<BankTransfer*>(p)) { bt->initiateTransfer(); }
}

// ✅ FIX — dùng virtual function
void handle(Payment* p) { p->process(); }

🚫 Production Anti-Pattern

Dùng dynamic_cast nhiều lần → class hierarchy thiết kế sai. Đa hình = bạn KHÔNG CẦN biết kiểu cụ thể. Exception: visitor pattern, serialization, thư viện bên thứ 3.

Sai lầm 3: Gọi virtual function trong constructor

cpp
class Base {
public:
    Base() { initialize(); }              // ⚠️ Gọi Base::initialize(), KHÔNG phải Derived!
    virtual void initialize() { std::cout << "Base\n"; }
};
class Derived : public Base {
public:
    void initialize() override { std::cout << "Derived\n"; }
};
// Derived d; → in "Base" (không phải "Derived"!)
// Lý do: trong constructor Base, vptr vẫn trỏ đến vtable của Base

Best Practices Checklist

✅ Checklist triển khai

Virtual Functions:

  • [ ] Đặt virtual ở base class cho mọi hàm cần override
  • [ ] Luôn dùng override ở derived class
  • [ ] Dùng final khi muốn chặn override tiếp
  • [ ] Không gọi virtual functions trong constructor/destructor

Destructors:

  • [ ] Class có virtual function → destructor PHẢI virtual
  • [ ] Dùng virtual ~Base() = default; nếu không cần logic đặc biệt
  • [ ] Derived destructor thêm override cho an toàn

Design:

  • [ ] Prefer pure virtual (interface) khi không có default behavior hợp lý
  • [ ] Tránh deep inheritance hierarchies (> 3 levels = code smell)
  • [ ] Composition over inheritance khi có thể
  • [ ] Tránh dynamic_cast — nếu cần, xem lại thiết kế

Performance:

  • [ ] Không tối ưu polymorphism khi chưa profile
  • [ ] Xem xét CRTP hoặc std::variant cho hot paths
  • [ ] Dùng final để giúp compiler devirtualize

🎉 Module 2 Hoàn Tất!

Chúc mừng! Bạn đã hoàn thành Module 2: OOP & System Integrity. Tổng kết những gì bạn đã nắm:

BàiChủ đềKỹ năng đạt được
04OOP FundamentalsClass, encapsulation, inheritance, access control
05RAII & ConstructorsResource management, Rule of Five, smart pointers
06Polymorphism & vtableVirtual functions, vtable, override/final, abstract classes

Tiếp theo: Module 3 — STL Weapons 🗡️

Module 3 sẽ trang bị cho bạn bộ công cụ mạnh nhất của C++: containers, iterators, và algorithms từ Standard Template Library.