Giao diện
🎭 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
virtualfunctions 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ểm | Static Dispatch | Dynamic Dispatch |
|---|---|---|
| Quyết định lúc nào | Compile time | Runtime |
| Từ khóa | Không cần virtual | Cần virtual |
| Dựa vào | Kiểu con trỏ | Kiểu thực tế của object |
| Linh hoạt | Không | Có — đ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ậtQuy 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:
- vtable (virtual table) — một bảng chứa con trỏ đến các hàm virtual, mỗi class có MỘT vtable
- 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 object có mộ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
override và final — 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:
finalcó 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
Icho 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-CLASS | N × 8 bytes (vtable) | Tạo 1 lần, shared bởi mọi object |
| Per-OBJECT | 8 bytes (vptr) | Dù có 1 hay 100 virtual functions |
| Per-CALL | ~1-2 nanoseconds | 1 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:
loggercó kiểuLogger*- Destructor không virtual → static dispatch → gọi
Logger::~Logger() FileLogger::~FileLogger()KHÔNG được gọifile_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 BaseBest 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
finalkhi 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
overridecho 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::variantcho 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ài | Chủ đề | Kỹ năng đạt được |
|---|---|---|
| 04 | OOP Fundamentals | Class, encapsulation, inheritance, access control |
| 05 | RAII & Constructors | Resource management, Rule of Five, smart pointers |
| 06 | Polymorphism & vtable | Virtual 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.