การเรียก virtual function ภายใน constructor หรือ destructor เป็น **อันตรายและควรหลีกเลี่ยง** เพราะพฤติกรรมไม่ได้เป็นไปตามที่คาดหวัง (ไม่ได้เรียก derived version)
เหตุผล: Construction/Destruction Order
- Construction: Base constructor รัน **ก่อน** Derived constructor
- Destruction: Derived destructor รัน **ก่อน** Base destructor
- ระหว่าง Base constructor รัน: Object **ยังไม่ใช่** Derived type → vtable ยังเป็นของ Base
ตัวอย่างปัญหาใน Constructor
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base constructor\n";
init(); // ❌ เรียก virtual function ใน constructor
}
virtual void init() {
cout << "Base::init()\n";
}
virtual ~Base() = default;
};
class Derived : public Base {
int value;
public:
Derived(int v) : value(v) {
cout << "Derived constructor\n";
}
void init() override {
cout << "Derived::init(), value = " << value << endl;
// value ยังไม่ได้ initialize! (Base constructor รันก่อน)
}
};
int main() {
cout << "Creating Derived(42):\n";
Derived d(42);
cout << "Done\n";
}
ผลลัพธ์ (แสดงปัญหา)
Creating Derived(42): Base constructor Base::init() // ← เรียก Base version! ไม่ใช่ Derived Derived constructor Done
ปัญหาเพิ่มเติม: ใช้ member ก่อน initialize
ในตัวอย่างข้างต้น Derived::init() เข้าถึง value แต่ value ยังไม่ถูก initialize (Derived constructor ยังไม่รัน) → **Undefined Behavior**
ตัวอย่างใน Destructor
class Base {
public:
virtual ~Base() {
cleanup(); // ❌ เรียก virtual ใน destructor
}
virtual void cleanup() { cout << "Base cleanup\n"; }
};
class Derived : public Base {
Resource* res;
public:
Derived() : res(new Resource) {}
~Derived() override {
delete res; // Derived cleanup ก่อน
res = nullptr;
}
void cleanup() override {
cout << "Derived cleanup\n";
// res ถูกลบแล้ว! (Base destructor รันหลัง Derived)
}
};
ผลลัพธ์ Destructor
Derived cleanup // จาก ~Derived() เรียก cleanup() ผ่าน virtual? ไม่ - มันเรียก Base::cleanup()! Base cleanup
จริงๆ แล้วใน destructor ของ Base vtable 已經กลายเป็น Base แล้ว (Derived part ถูกทำลายไปแล้ว) ดังนั้นเรียก Base::cleanup()
วิธีแก้ไขที่ปลอดภัย
1. Non-Virtual Interface (NVI) Pattern
class Base {
public:
Base() { initialize(); } // เรียก non-virtual
void initialize() { // Non-virtual public interface
// Common initialization
doInitialize(); // เรียก private virtual
}
virtual ~Base() {
cleanup();
}
void cleanup() { // Non-virtual
doCleanup();
}
private:
virtual void doInitialize() { cout << "Base init\n"; }
virtual void doCleanup() { cout << "Base cleanup\n"; }
};
class Derived : public Base {
private:
void doInitialize() override { cout << "Derived init\n"; }
void doCleanup() override { cout << "Derived cleanup\n"; }
};
2. Two-Phase Initialization
class Base {
public:
Base() {} // ไม่เรียก virtual
// เรียกหลัง construction เสร็จ
virtual bool init() { return true; }
// Factory method
static unique_ptr<Base> create() {
auto ptr = make_unique<Derived>();
if (ptr->init()) return ptr;
return nullptr;
}
};
3. ใช้ CRTP (Curiously Recurring Template Pattern)
template <typename Derived>
class Base {
public:
Base() {
static_cast<Derived*>(this)->init(); // Static dispatch
}
};
class Derived : public Base<Derived> {
public:
void init() { cout << "Derived init\n"; }
};
Best Practices
- ❌ **อย่า** เรียก virtual functions ใน constructor/destructor
- ✅ ใช้ **NVI pattern** (public non-virtual เรียก private virtual)
- ✅ ใช้ **Two-phase initialization** (init() แยกต่างหาก)
- ✅ ใช้ **Factory functions** สำหรับ object creation
- ✅ ใช้ **CRTP** สำหรับ static polymorphism
ข้อยกเว้น: Pure Virtual Destructor
Pure virtual destructor **ต้องมี body** และถูกเรียกปกติใน destruction chain (ไม่ใช่ virtual call ปกติ)