Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C++

Virtual Base Class ใน C++: แก้ปัญหา Diamond Inheritance

Virtual base class ใช้แก้ปัญหา **Diamond Problem** (Diamond Inheritance) ที่ derived class ได้รับ base class มาหลายคอผ่านการสืบทอดหลายเส้นทาง

ปัญหา Diamond Inheritance

      Base
     /    \
   D1      D2
     \    /
      Derived

โดยไม่มี virtual inheritance: Derived จะมี **สองคอ** ของ Base (หนึ่งจาก D1, หนึ่งจาก D2) → ambiguity

ตัวอย่างปัญหา (ไม่ใช้ virtual)

#include <iostream>

using namespace std;

class Base {
public:
    int value = 10;
};

class D1 : public Base {  // Non-virtual
public:
    int d1 = 20;
};

class D2 : public Base {  // Non-virtual
public:
    int d2 = 30;
};

class Derived : public D1, public D2 {
public:
    int d3 = 40;
};

int main() {
    Derived obj;
    
    // obj.value = 100;  // ❌ COMPILE ERROR: ambiguous! 
    // obj.value = 200;  // ❌ ไม่รู้ว่า value ของ D1::Base หรือ D2::Base
    
    // ต้องระบุ path ชัดเจน:
    obj.D1::value = 100;
    obj.D2::value = 200;
    
    cout << "D1::value = " << obj.D1::value << endl;
    cout << "D2::value = " << obj.D2::value << endl;
    cout << "d1 = " << obj.d1 << endl;
    cout << "d2 = " << obj.d2 << endl;
    cout << "d3 = " << obj.d3 << endl;
    
    return 0;
}

ผลลัพธ์ (ไม่ใช้ virtual)

D1::value = 100
D2::value = 200
d1 = 20
d2 = 30
d3 = 40

แก้ไขด้วย Virtual Base Class

class Base {
public:
    int value = 10;
};

class D1 : virtual public Base {  // เพิ่ม virtual
public:
    int d1 = 20;
};

class D2 : virtual public Base {  // เพิ่ม virtual
public:
    int d2 = 30;
};

class Derived : public D1, public D2 {
public:
    int d3 = 40;
};

int main() {
    Derived obj;
    
    obj.value = 100;  // ✅ OK: มี Base เพียง 1 คอ
    obj.value = 200;  // เขียนทับค่าเดิม
    
    cout << "value = " << obj.value << endl;  // 200
    cout << "d1 = " << obj.d1 << endl;
    cout << "d2 = " << obj.d2 << endl;
    cout << "d3 = " << obj.d3 << endl;
    
    return 0;
}

ผลลัพธ์ (ใช้ virtual)

value = 200
d1 = 20
d2 = 30
d3 = 40

หลักการทำงานของ Virtual Base Class

  • Compiler รับประกันว่ามี **เพียงหนึ่ง subobject** ของ virtual base class ใน complete object
  • Derived class (most derived) **รับผิดชอบ initialize virtual base** โดยตรง
  • Virtual base ถูกสร้าง **ก่อน** non-virtual bases

Constructor Initialization Order

class Base {
public:
    Base(int v) : value(v) { cout << "Base(" << v << ")\n"; }
    int value;
};

class D1 : virtual public Base {
public:
    D1(int v) : Base(v * 10) { cout << "D1\n"; }
};

class D2 : virtual public Base {
public:
    D2(int v) : Base(v * 100) { cout << "D2\n"; }
};

class Derived : public D1, public D2 {
public:
    // Derived ต้อง initialize Base เอง (ignore D1, D2 init of Base)
    Derived() : Base(999), D1(1), D2(2) { 
        cout << "Derived\n"; 
    }
};

// Output:
// Base(999)
// D1
// D2
// Derived

ข้อควรระวัง

  • Performance: Virtual base ต้องการ pointer/offset เพิ่มเติม (space/time overhead)
  • Complexity: Initialization order ซับซ้อนกว่า
  • Design: มักบ่งชี้ design flaw - ควรพิจารณา composition แทน inheritance
  • Pure virtual destructor: Virtual base มักควรมี virtual destructor

สรุป

Virtual base class แก้ปัญหา diamond inheritance โดยบังคับให้มี base subobject เพียงหนึ่งคอ ใช้คำสำคัญ virtual ในการสืบทอดระดับกลาง (class D1 : virtual public Base) และ most-derived class ต้อง initialize virtual base เอง