سؤال

نحن جميعا نعرف ما الظاهري وظائف في C++, ولكن كيف يتم تنفيذها على مستوى عميق?

يمكن vtable تعديلها أو حتى الوصول إليها مباشرة في وقت التشغيل?

لا vtable موجودة في جميع الطبقات ، أو فقط تلك التي لديها واحد على الأقل الظاهري وظيفة ؟

هل فئات مجردة ببساطة فارغة على وظيفة مؤشر واحد على الأقل الدخول ؟

لا وجود ظاهري واحد وظيفة تبطئ الصف كله ؟ أو استدعاء دالة الظاهري?و هل سرعة نتأثر إذا كانت دالة الظاهري هو في الواقع الكتابة أو لا, أو هل هذا له أي تأثير طالما أنه الظاهري.

هل كانت مفيدة؟

المحلول

كيف هي افتراضية وظائف تنفيذها على مستوى عميق?

من "وظائف افتراضية في C++":

كلما برنامج دالة ظاهري أعلن الخامس - الجدول هي التي شيدت لهذه الدرجة.V-الجدول يتكون من عناوين افتراضية وظائف الفئات التي تحتوي على واحد أو أكثر من وظائف افتراضية.والهدف من الطبقة التي تحتوي الظاهري يحتوي على دالة الظاهري المؤشر الذي يشير إلى قاعدة عنوان الجدول الظاهري في الذاكرة.كلما كان هناك استدعاء دالة ظاهري ، v-الجدول يستخدم لحل وظيفة العنوان.كائن من الفئة التي تحتوي على واحد أو أكثر من وظائف افتراضية يحتوي على مؤشر الظاهري يسمى vptr في بداية جدا من وجوه في الذاكرة.وبالتالي حجم الكائن في هذه الحالة يزيد من حجم المؤشر.هذا vptr يحتوي على قاعدة عنوان الجدول الظاهري في الذاكرة.لاحظ أن الجداول الافتراضية هي فئة محددة ، أي هناك واحد فقط من الجدول الظاهري لفئة بغض النظر عن عدد من الوظائف الافتراضية التي يحتوي عليها.هذا الجدول الظاهري بدوره يحتوي على قاعدة عناوين واحدة أو أكثر من الوظائف الافتراضية من الدرجة.في الوقت الذي دالة ظاهري يسمى على كائن ، vptr هذا الكائن يوفر قاعدة عنوان الجدول الظاهري لتلك الفئة في الذاكرة.هذا الجدول يستخدم لحل استدعاء دالة كما أنه يحتوي على عناوين جميع وظائف افتراضية من تلك الفئة.هذا هو كيفية الربط الديناميكي يتم حلها من خلال استدعاء دالة ظاهري.

يمكن vtable تعديلها أو حتى الوصول إليها مباشرة في وقت التشغيل?

عالميا, أعتقد أن الجواب هو "لا".هل يمكن أن تفعل بعض الذاكرة تغيير اسم للعثور على vtable ولكن كنت لا تزال لا تعرف ما هي وظيفة التوقيع يبدو أن نسميها.أي شيء كنت ترغب في تحقيق هذه القدرة (أن يدعم اللغة) يجب أن يكون ممكنا من دون الوصول إلى vtable مباشرة أو تعديله في وقت التشغيل.نلاحظ أيضا, C++ لغة المواصفات لا تحديد vtables المطلوب - ولكن هذه هي الطريقة الأكثر القائمون على تنفيذ وظائف افتراضية.

لا vtable موجودة في جميع الكائنات ، أو فقط تلك التي لديها واحد على الأقل الظاهري وظيفة ؟

أنا تصدق الجواب هنا هو "ذلك يعتمد على تنفيذ" منذ المواصفات لا تتطلب vtables في المقام الأول.ومع ذلك, في الواقع, أعتقد أن كل الحديث المجمعين فقط إنشاء vtable إذا فئة لديها ما لا يقل عن 1 الظاهري وظيفة.هناك مساحة النفقات العامة المرتبطة vtable ووقت النفقات العامة المرتبطة استدعاء دالة ظاهري مقابل دالة غير ظاهري.

هل فئات مجردة ببساطة فارغة على وظيفة مؤشر واحد على الأقل الدخول ؟

الجواب هو غير محدد من اللغة المواصفات لذلك يعتمد على التنفيذ.استدعاء دالة ظاهرية خالصة النتائج في غير معرف السلوك إذا لم يتم تعريف (التي عادة ما تكون ليس) (ISO/IEC 14882:2003 10.4-2).في واقع الامر انه لا تخصيص فتحة في vtable عن وظيفة ولكن لا تعيين عنوان لها.هذا يترك vtable غير مكتملة مما يتطلب الفئات المشتقة لتنفيذ وظيفة واستكمال vtable.بعض التطبيقات لا ببساطة وضع مؤشر فارغة في vtable الدخول ؛ تطبيقات أخرى مكان مؤشر إلى طريقة دمية أن يفعل شيئا من هذا القبيل إلى تأكيد.

علما بأن فئة مجردة يمكن تعريف تنفيذ دالة ظاهري ، ولكن هذه الوظيفة لا يمكن إلا أن يسمى مع مؤهل-id الجملة (أي. ، تماما تحديد فئة في اسم الأسلوب ، على غرار استدعاء أسلوب فئة أساسية من فئة مشتقة).هذا ويتم توفير وسيلة سهلة لاستخدام الافتراضي التنفيذ ، في حين لا تزال تتطلب أن فئة مشتقة توفير التجاوز.

لا وجود ظاهري واحد وظيفة تبطئ الصف بأكمله أو فقط استدعاء دالة الظاهري?

هذا هو الحصول على إلى حافة المعرفة ، لذلك شخص الرجاء مساعدتي هنا إذا كنت مخطئا!

أنا تصدق أن الوظائف التي الافتراضية في الصف تجربة الأداء في الوقت ضرب المتعلقة استدعاء دالة ظاهري مقابلغير دالة الظاهري.المساحة العامة لفئة هل هناك أي طريقة.ملاحظة أنه إذا كان هناك vtable ، هناك فقط 1 في الدرجة, لا أحد في وجوه.

هل سرعة نتأثر إذا كانت دالة الظاهري هو في الواقع تجاوزها أو لا, أو هل هذا له أي تأثير طالما هو الافتراضي?

أنا لا أصدق وقت تنفيذ دالة ظاهري الذي تجاوز النقصان مقارنة استدعاء قاعدة الظاهري وظيفة.ومع ذلك ، هناك مساحة إضافية النفقات العامة عن فئة المرتبطة تعريف آخر vtable عن فئة مشتقة مقابل الفئة الأساسية.

مصادر إضافية:

http://www.codersource.net/published/view/325/virtual_functions_in.aspx (عبر طريق العودة الجهاز)
http://en.wikipedia.org/wiki/Virtual_table
http://www.codesourcery.com/public/cxx-abi/abi.html#vtable

نصائح أخرى

  • يمكن vtable تعديلها أو حتى الوصول إليها مباشرة في وقت التشغيل?

لا portably, ولكن إذا كنت لا تمانع في الحيل القذرة ، بالتأكيد!

تحذير:هذا الأسلوب غير مستحسن للاستخدام من قبل الأطفال البالغين تحت سن 969, أو صغيرة المخلوقات فروي من ألفا قنطورس.قد تشمل الآثار الجانبية الشياطين التي تخرج من الأنف, المفاجئ مظهر من Yog-Sothoth كما مطلوب الموافق على جميع اللاحقة استعراض الرموز أو بأثر رجعي إضافة IHuman::PlayPiano() أن جميع الحالات الموجودة]

في معظم المجمعين رأيت ، vtbl * هو أول 4 بايت من وجوه ، vtbl محتويات هي ببساطة مجموعة من الأعضاء هناك مؤشرات (عموما في الأمر أنهم كانوا أعلنت مع قاعدة الطبقة الأولى).هناك بالطبع أخرى تخطيطات ممكن, ولكن هذا ما كنت عموما لوحظ.

class A {
  public:
  virtual int f1() = 0;
};
class B : public A {
  public:
  virtual int f1() { return 1; }
  virtual int f2() { return 2; }
};
class C : public A {
  public:
  virtual int f1() { return -1; }
  virtual int f2() { return -2; }
};

A *x = new B;
A *y = new C;
A *z = new C;

الآن لسحب بعض الاشكالات...

تغيير الفئة في وقت التشغيل:

std::swap(*(void **)x, *(void **)y);
// Now x is a C, and y is a B! Hope they used the same layout of members!

استبدال طريقة لجميع الحالات (monkeypatching فئة)

هذا واحد من أصعب قليلا لأن vtbl نفسها هي ربما في ذاكرة القراءة فقط.

int f3(A*) { return 0; }

mprotect(*(void **)x,8,PROT_READ|PROT_WRITE|PROT_EXEC);
// Or VirtualProtect on win32; this part's very OS-specific
(*(int (***)(A *)x)[0] = f3;
// Now C::f1() returns 0 (remember we made x into a C above)
// so x->f1() and z->f1() both return 0

هذا الأخير بدلا من المرجح أن تجعل الفيروس-لعبة الداما على الرابط وذلك بسبب mprotect التلاعب.في عملية استخدام NX بت قد تفشل.

لا وجود ظاهري واحد وظيفة تبطئ الصف كله ؟

أو استدعاء دالة الظاهري?و هل سرعة نتأثر إذا كانت دالة الظاهري هو في الواقع الكتابة أو لا, أو هل هذا له أي تأثير طالما أنه الظاهري.

وجود وظائف افتراضية يبطئ الصف بأكمله طالما بند واحد من البيانات يجب تهيئة نسخها ... عند التعامل مع كائن من هذه الفئة.الفئة مع نصف دزينة من أعضاء أو نحو ذلك ، يجب أن يكون اختلاف neglible.فئة الذي يحتوي على واحد char عضو أو أي أعضاء في جميع الفرق قد يكون ملحوظا.

وبصرف النظر عن ذلك ، من المهم أن نلاحظ أن ليس كل استدعاء دالة ظاهري هو استدعاء دالة ظاهري.إذا كان لديك كائن من النوع المعروف, المترجم يمكن أن تنبعث من رمز وظيفة طبيعية الاحتجاج ، وحتى يمكن مضمنة قال الوظيفة إذا كان يشعر مثل ذلك.انها فقط عندما تفعل متعددة الأشكال المكالمات عبر مؤشر أو المراجع التي قد النقطة كائن من الفئة الأساسية أو في كائن من فئة مشتقة أن كنت في حاجة إلى vtable المراوغة و دفع ثمنها من حيث الأداء.

struct Foo { virtual ~Foo(); virtual int a() { return 1; } };
struct Bar: public Foo { int a() { return 2; } };
void f(Foo& arg) {
  Foo x; x.a(); // non-virtual: always calls Foo::a()
  Bar y; y.a(); // non-virtual: always calls Bar::a()
  arg.a();      // virtual: must dispatch via vtable
  Foo z = arg;  // copy constructor Foo::Foo(const Foo&) will convert to Foo
  z.a();        // non-virtual Foo::a, since z is a Foo, even if arg was not
}

الخطوات الأجهزة يجب أن تأخذ هي أساسا نفس ، بغض النظر عن ما إذا كانت وظيفة الكتابة أو لا.عنوان vtable قراءة من وجوه ، مؤشر الدالة استردادها من الفتحة المناسبة ، وظيفة تسمى من قبل المؤشر.من حيث الأداء الفعلي ، فرع التنبؤات قد يكون له بعض التأثير.لذلك على سبيل المثال ، إذا كان معظم الأشياء الخاصة بك تشير إلى نفس تطبيق معين الظاهري وظيفة ، ثم هناك بعض فرصة أن فرع التوقع سوف التنبؤ بشكل صحيح التي تعمل على الاتصال حتى قبل المؤشر تم استردادها.ولكن لا يهم أي وظيفة هو مشترك واحد:يمكن أن تكون معظم الكائنات تفويض غير الكتابة حالة قاعدة ، أو معظم الكائنات التي تنتمي إلى نفس الفئة الفرعية وبالتالي تفويض إلى نفس الكتابة الحالة.

كيف يتم تنفيذها على مستوى عميق?

أنا أحب فكرة jheriko لإثبات هذا باستخدام نموذج التنفيذ.ولكن أود أن استخدام C لتنفيذ شيء أقرب إلى رمز أعلاه ، حتى أن انخفاض مستوى هو أكثر رؤيتها بسهولة.

الفئة الأصل فو

typedef struct Foo_t Foo;   // forward declaration
struct slotsFoo {           // list all virtual functions of Foo
  const void *parentVtable; // (single) inheritance
  void (*destructor)(Foo*); // virtual destructor Foo::~Foo
  int (*a)(Foo*);           // virtual function Foo::a
};
struct Foo_t {                      // class Foo
  const struct slotsFoo* vtable;    // each instance points to vtable
};
void destructFoo(Foo* self) { }     // Foo::~Foo
int aFoo(Foo* self) { return 1; }   // Foo::a()
const struct slotsFoo vtableFoo = { // only one constant table
  0,                                // no parent class
  destructFoo,
  aFoo
};
void constructFoo(Foo* self) {      // Foo::Foo()
  self->vtable = &vtableFoo;        // object points to class vtable
}
void copyConstructFoo(Foo* self,
                      Foo* other) { // Foo::Foo(const Foo&)
  self->vtable = &vtableFoo;        // don't copy from other!
}

فئة مشتقة بار

typedef struct Bar_t {              // class Bar
  Foo base;                         // inherit all members of Foo
} Bar;
void destructBar(Bar* self) { }     // Bar::~Bar
int aBar(Bar* self) { return 2; }   // Bar::a()
const struct slotsFoo vtableBar = { // one more constant table
  &vtableFoo,                       // can dynamic_cast to Foo
  (void(*)(Foo*)) destructBar,      // must cast type to avoid errors
  (int(*)(Foo*)) aBar
};
void constructBar(Bar* self) {      // Bar::Bar()
  self->base.vtable = &vtableBar;   // point to Bar vtable
}

وظيفة و أداء الظاهري استدعاء دالة

void f(Foo* arg) {                  // same functionality as above
  Foo x; constructFoo(&x); aFoo(&x);
  Bar y; constructBar(&y); aBar(&y);
  arg->vtable->a(arg);              // virtual function call
  Foo z; copyConstructFoo(&z, arg);
  aFoo(&z);
  destructFoo(&z);
  destructBar(&y);
  destructFoo(&x);
}

حتى تستطيع أن ترى ، vtable هو مجرد كتلة ثابتة في الذاكرة ، في الغالب تحتوي على مؤشرات الدالة.كل كائن من متعدد الأشكال الدرجة تشير إلى vtable المقابلة إلى نوع الديناميكي.هذا أيضا يجعل الاتصال بين RTTI و وظائف افتراضية أكثر وضوحا:يمكنك التحقق من أي نوع فئة ببساطة من خلال النظر في ما vtable هو.أعلاه هو المبسطة في العديد من الطرق مثلوراثة متعددة ، ولكن المفهوم العام هو الصوت.

إذا arg هو من نوع Foo* و تأخذ arg->vtable, لكن هو في الحقيقة كائن من نوع Bar, ثم كنت لا تزال تحصل على العنوان الصحيح من vtable.ذلك لأن vtable هو دائما أول عنصر في عنوان الكائن ، بغض النظر عن ما إذا كان هذا يسمى vtable أو base.vtable في صحيح كتبته التعبير.

وعادة مع VTable ، مجموعة من مؤشرات على وظائف.

هذا الجواب قد أدرجت في مجتمع ويكي الإجابة

  • هل فئات مجردة ببساطة فارغة على وظيفة مؤشر واحد على الأقل الدخول ؟

الجواب على ذلك هو أنه غير محدد - استدعاء دالة ظاهرية خالصة النتائج في غير معرف السلوك إذا لم يتم تعريف (التي عادة ما تكون ليس) (ISO/IEC 14882:2003 10.4-2).بعض التطبيقات لا ببساطة وضع مؤشر فارغة في vtable الدخول ؛ تطبيقات أخرى مكان مؤشر إلى طريقة دمية أن يفعل شيئا من هذا القبيل إلى تأكيد.

علما بأن فئة مجردة يمكن تعريف تنفيذ دالة ظاهري ، ولكن هذه الوظيفة لا يمكن إلا أن يسمى مع مؤهل-id الجملة (أي. ، تماما تحديد فئة في اسم الأسلوب ، على غرار استدعاء أسلوب فئة أساسية من فئة مشتقة).هذا ويتم توفير وسيلة سهلة لاستخدام الافتراضي التنفيذ ، في حين لا تزال تتطلب أن فئة مشتقة توفير التجاوز.

يمكنك إعادة إنشاء وظيفة من وظائف افتراضية في C++ باستخدام مؤشرات الدالة أعضاء من فئة وظائف ثابتة مثل تطبيقات أو باستخدام مؤشر إلى وظائف الأعضاء ووظائف الأعضاء من أجل التنفيذ.هناك فقط الترميزية المزايا بين الطريقتين...في الواقع الافتراضي وظيفة المكالمات فقط الترميزية الراحة أنفسهم.في الواقع الميراث هو مجرد الترميزية الراحة...فإنه يمكن تنفيذها دون استخدام ميزات اللغة من أجل الميراث.:)

أدناه هو حماقة لم تختبر ، وربما عربات التي تجرها الدواب رمز ، ولكن نأمل أن يوضح هذه الفكرة.

على سبيل المثال

class Foo
{
protected:
 void(*)(Foo*) MyFunc;
public:
 Foo() { MyFunc = 0; }
 void ReplciatedVirtualFunctionCall()
 {
  MyFunc(*this);
 }
...
};

class Bar : public Foo
{
private:
 static void impl1(Foo* f)
 {
  ...
 }
public:
 Bar() { MyFunc = impl1; }
...
};

class Baz : public Foo
{
private:
 static void impl2(Foo* f)
 {
  ...
 }
public:
 Baz() { MyFunc = impl2; }
...
};

سأحاول جعلها بسيطة :)

نحن جميعا نعرف ما الظاهري وظائف في C++, ولكن كيف يتم تنفيذها على مستوى عميق?

هذا هو صفيف مؤشرات الوظائف ، والتي هي تطبيقات معينة دالة الظاهري.فهرس في هذا يمثل مجموعة معينة مؤشر دالة ظاهري المحددة للحصول على الدرجة.وهذا يشمل نقية وظائف افتراضية.

عندما متعدد الأشكال الدرجة مستمد من أخرى متعددة الأشكال الفئة ، قد يكون لدينا الحالات التالية:

  • فإن اشتقاق فئة لا تضيف جديد وظائف افتراضية ولا يتجاوز أي.في هذه الحالة هذه الفئة أسهم vtable مع الفئة الأساسية.
  • فإن اشتقاق فئة يضيف و يلغي طرق افتراضية.في هذه الحالة يحصل الخاصة vtable ، حيث أضاف وظائف افتراضية يكون مؤشر بدء الماضي الماضي مشتقة واحد.
  • متعددة الأشكال دروس في الميراث.في هذه الحالة لدينا مؤشر التحول بين الثاني و التالي قواعد ومؤشر في فئة مشتقة

يمكن vtable تعديلها أو حتى الوصول إليها مباشرة في وقت التشغيل?

لا الطريقة القياسية - لا يوجد API للوصول إليها.المجمعين قد يكون لها بعض الإضافات أو واجهات برمجة التطبيقات الخاصة للوصول إليها, ولكن قد يكون ذلك إلا امتداد.

لا vtable موجودة في جميع الطبقات ، أو فقط تلك التي لديها واحد على الأقل الظاهري وظيفة ؟

فقط تلك التي لديها واحد على الأقل الظاهري وظيفة (حتى المدمر) أو اشتقاق فئة واحدة على الأقل لها vtable ("هو متعدد الأشكال").

هل فئات مجردة ببساطة فارغة على وظيفة مؤشر واحد على الأقل الدخول ؟

هذا التنفيذ ممكنا ، بل لا يمارس.بدلا من ذلك عادة ما يكون هناك وظيفة أن يطبع شيئا مثل "نقية وظيفة افتراضية تسمى" و لا abort().الدعوة التي قد تحدث عند محاولة استدعاء الأسلوب التجريدي في منشئ أو المدمر.

لا وجود ظاهري واحد وظيفة تبطئ الصف كله ؟ أو استدعاء دالة الظاهري?و هل سرعة نتأثر إذا كانت دالة الظاهري هو في الواقع الكتابة أو لا, أو هل هذا له أي تأثير طالما أنه الظاهري.

التباطؤ يعتمد فقط على ما إذا كان الاتصال يتم حل مثل الاتصال المباشر أو الاتصال الافتراضية.و لا شيء آخر يهم.:)

إذا كنت استدعاء دالة ظاهري من خلال مؤشر أو مرجع إلى كائن, ثم أنها سوف تكون دائما تنفيذها الاتصال الافتراضية - لأن المترجم لا يمكن أبدا أن تعرف ما هو نوع من كائن سيتم تعيين هذا المؤشر في وقت التشغيل ، سواء كان من فئة في هذا الأسلوب هو تجاوز أو لا.إلا في حالتين مترجم حل الدعوة إلى الافتراضي بمثابة دعوة مباشرة:

  • إذا كنت استدعاء الأسلوب من خلال قيمة (متغير أو نتيجة الدالة بإرجاع قيمة) - في هذه الحالة مترجم لا يوجد لديه شكوك ما الفعلي فئة الكائن ، أن "من الصعب حلها" في وقت الترجمة.
  • إذا كان أسلوب الظاهري أعلن final في الطبقة التي لديك مؤشر أو المرجعية التي نسميها (فقط في C++11).في هذه الحالة مترجم يعرف أن هذا الأسلوب لا يمكن أن يخضع أي تجاوز و لا يمكن إلا أن يكون طريقة من هذه الفئة.

ملاحظة على الرغم من أنه الظاهري المكالمات فقط النفقات العامة من dereferencing اثنين من المؤشرات.باستخدام RTTI (على الرغم من متوفرة فقط على فئات متعددة الأشكال) أبطأ من الاتصال الافتراضية وأساليب يجب أن تجد قضية لتنفيذ نفس الشيء اثنين من هذه الطرق.على سبيل المثال ، تحديد virtual bool HasHoof() { return false; } ثم تجاوز فقط bool Horse::HasHoof() { return true; } سوف توفر لك مع القدرة على استدعاء if (anim->HasHoof()) من شأنها أن تكون أسرع من محاولة if(dynamic_cast<Horse*>(anim)).وهذا لأن dynamic_cast على المشي من خلال التسلسل الهرمي فئة في بعض الحالات حتى متكرر لمعرفة ما إذا كان هناك يمكن بناء مسار من الفعلية نوع المؤشر المطلوب نوع الفئة.في حين أن الاتصال الافتراضية هو نفسه دائما - dereferencing اثنين من المؤشرات.

هنا runnable دليل تنفيذ الجدول الظاهري في الحديث C++.وقد محددة جيدا دلالات لا الخارقة لا void*.

ملاحظة: .* و ->* هي شركات مختلفة من * و ->.الأعضاء مؤشرات الدالة تعمل بشكل مختلف.

#include <iostream>
#include <vector>
#include <memory>

struct vtable; // forward declare, we need just name

class animal
{
public:
    const std::string& get_name() const { return name; }

    // these will be abstract
    bool has_tail() const;
    bool has_wings() const;
    void sound() const;

protected: // we do not want animals to be created directly
    animal(const vtable* vtable_ptr, std::string name)
    : vtable_ptr(vtable_ptr), name(std::move(name)) { }

private:
    friend vtable; // just in case for non-public methods

    const vtable* const vtable_ptr;
    std::string name;
};

class cat : public animal
{
public:
    cat(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does meow\n"; 
    }
};

class dog : public animal
{
public:
    dog(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return true; }
    bool has_wings() const { return false; }
    void sound() const
    {
        std::cout << get_name() << " does whoof\n"; 
    }
};

class parrot : public animal
{
public:
    parrot(std::string name);

    // functions to bind dynamically
    bool has_tail() const { return false; }
    bool has_wings() const { return true; }
    void sound() const
    {
        std::cout << get_name() << " does crrra\n"; 
    }
};

// now the magic - pointers to member functions!
struct vtable
{
    bool (animal::* const has_tail)() const;
    bool (animal::* const has_wings)() const;
    void (animal::* const sound)() const;

    // constructor
    vtable (
        bool (animal::* const has_tail)() const,
        bool (animal::* const has_wings)() const,
        void (animal::* const sound)() const
    ) : has_tail(has_tail), has_wings(has_wings), sound(sound) { }
};

// global vtable objects
const vtable vtable_cat(
    static_cast<bool (animal::*)() const>(&cat::has_tail),
    static_cast<bool (animal::*)() const>(&cat::has_wings),
    static_cast<void (animal::*)() const>(&cat::sound));
const vtable vtable_dog(
    static_cast<bool (animal::*)() const>(&dog::has_tail),
    static_cast<bool (animal::*)() const>(&dog::has_wings),
    static_cast<void (animal::*)() const>(&dog::sound));
const vtable vtable_parrot(
    static_cast<bool (animal::*)() const>(&parrot::has_tail),
    static_cast<bool (animal::*)() const>(&parrot::has_wings),
    static_cast<void (animal::*)() const>(&parrot::sound));

// set vtable pointers in constructors
cat::cat(std::string name) : animal(&vtable_cat, std::move(name)) { }
dog::dog(std::string name) : animal(&vtable_dog, std::move(name)) { }
parrot::parrot(std::string name) : animal(&vtable_parrot, std::move(name)) { }

// implement dynamic dispatch
bool animal::has_tail() const
{
    return (this->*(vtable_ptr->has_tail))();
}

bool animal::has_wings() const
{
    return (this->*(vtable_ptr->has_wings))();
}

void animal::sound() const
{
    (this->*(vtable_ptr->sound))();
}

int main()
{
    std::vector<std::unique_ptr<animal>> animals;
    animals.push_back(std::make_unique<cat>("grumpy"));
    animals.push_back(std::make_unique<cat>("nyan"));
    animals.push_back(std::make_unique<dog>("doge"));
    animals.push_back(std::make_unique<parrot>("party"));

    for (const auto& a : animals)
        a->sound();

    // note: destructors are not dispatched virtually
}

كل كائن لديه vtable مؤشر يشير إلى مجموعة من وظائف الأعضاء.

شيء ما لم يتم هنا ذكر جميع هذه الإجابات هو أنه في حالة وراثة متعددة ، حيث قاعدة الطبقات جميعها طرق افتراضية.وراثة الطبقة متعددة المؤشرات إلى vmt.والنتيجة هي أن حجم كل مثيل من مثل هذا الكائن هو أكبر.يعلم الجميع أن فئة مع طرق افتراضية 4 بايت إضافية عن vmt ، ولكن في حالة وراثة متعددة لكل قاعدة الطبقة التي الظاهري أساليب مرات 4.4 يجري حجم المؤشر.

قوي البنية إجابات صحيحة ماعدا السؤال:

هل فئات مجردة ببساطة فارغة على وظيفة مؤشر واحد على الأقل الدخول ؟

الجواب هو أنه لا يوجد الظاهري إنشاء الجدول في كل فئات مجردة.ليست هناك حاجة لأن أي كائنات من هذه الفئات يمكن أن تنشأ!

وبعبارة أخرى إذا كان لدينا:

class B { ~B() = 0; }; // Abstract Base class
class D : public B { ~D() {} }; // Concrete Derived class

D* pD = new D();
B* pB = pD;

على vtbl المؤشر الوصول إليها من خلال pB سيكون vtbl من الفئة D.هذا هو بالضبط كيف تعدد الأشكال تنفيذها.هذا هو كيف د طرق الوصول إليها من خلال pB.هناك حاجة vtbl عن فئة B.

ردا على مايك التعليق أدناه...

إذا الفئة B في الوصف لديه أسلوب الظاهري فو() أن لا يتم تجاوزها من قبل د و أسلوب الظاهري بار() هذا هو تجاوزها ، ثم د vtbl سوف يكون مؤشر إلى B فو() و إلى جانبها بار().لا يوجد vtbl خلق B.

لطيف جدا إثبات المفهوم جعلت قليلا في وقت سابق(لمعرفة ما إذا كان ترتيب الإرث المسائل);اسمحوا لي أن أعرف إذا كان تنفيذ C++ في الواقع يرفض ذلك(نسختي من دول مجلس التعاون الخليجي فقط يعطي تحذير إسناد مجهول البنيات ، ولكن هذا الخلل), أنا الغريب.

CCPolite.ح:

#ifndef CCPOLITE_H
#define CCPOLITE_H

/* the vtable or interface */
typedef struct {
    void (*Greet)(void *);
    void (*Thank)(void *);
} ICCPolite;

/**
 * the actual "object" literal as C++ sees it; public variables be here too 
 * all CPolite objects use(are instances of) this struct's structure.
 */
typedef struct {
    ICCPolite *vtbl;
} CPolite;

#endif /* CCPOLITE_H */

CCPolite_constructor.ح:

/** 
 * unconventionally include me after defining OBJECT_NAME to automate
 * static(allocation-less) construction.
 *
 * note: I assume CPOLITE_H is included; since if I use anonymous structs
 *     for each object, they become incompatible and cause compile time errors
 *     when trying to do stuff like assign, or pass functions.
 *     this is similar to how you can't pass void * to windows functions that
 *         take handles; these handles use anonymous structs to make 
 *         HWND/HANDLE/HINSTANCE/void*/etc not automatically convertible, and
 *         require a cast.
 */
#ifndef OBJECT_NAME
    #error CCPolite> constructor requires object name.
#endif

CPolite OBJECT_NAME = {
    &CCPolite_Vtbl
};

/* ensure no global scope pollution */
#undef OBJECT_NAME

الرئيسية.ج:

#include <stdio.h>
#include "CCPolite.h"

// | A Greeter is capable of greeting; nothing else.
struct IGreeter
{
    virtual void Greet() = 0;
};

// | A Thanker is capable of thanking; nothing else.
struct IThanker
{
    virtual void Thank() = 0;
};

// | A Polite is something that implements both IGreeter and IThanker
// | Note that order of implementation DOES MATTER.
struct IPolite1 : public IGreeter, public IThanker{};
struct IPolite2 : public IThanker, public IGreeter{};

// | implementation if IPolite1; implements IGreeter BEFORE IThanker
struct CPolite1 : public IPolite1
{
    void Greet()
    {
        puts("hello!");
    }

    void Thank()
    {
        puts("thank you!");
    }
};

// | implementation if IPolite1; implements IThanker BEFORE IGreeter
struct CPolite2 : public IPolite2
{
    void Greet()
    {
        puts("hi!");
    }

    void Thank()
    {
        puts("ty!");
    }
};

// | imposter Polite's Greet implementation.
static void CCPolite_Greet(void *)
{
    puts("HI I AM C!!!!");
}

// | imposter Polite's Thank implementation.
static void CCPolite_Thank(void *)
{
    puts("THANK YOU, I AM C!!");
}

// | vtable of the imposter Polite.
ICCPolite CCPolite_Vtbl = {
    CCPolite_Thank,
    CCPolite_Greet    
};

CPolite CCPoliteObj = {
    &CCPolite_Vtbl
};

int main(int argc, char **argv)
{
    puts("\npart 1");
    CPolite1 o1;
    o1.Greet();
    o1.Thank();

    puts("\npart 2");    
    CPolite2 o2;    
    o2.Greet();
    o2.Thank();    

    puts("\npart 3");    
    CPolite1 *not1 = (CPolite1 *)&o2;
    CPolite2 *not2 = (CPolite2 *)&o1;
    not1->Greet();
    not1->Thank();
    not2->Greet();
    not2->Thank();

    puts("\npart 4");        
    CPolite1 *fake = (CPolite1 *)&CCPoliteObj;
    fake->Thank();
    fake->Greet();

    puts("\npart 5");        
    CPolite2 *fake2 = (CPolite2 *)fake;
    fake2->Thank();
    fake2->Greet();

    puts("\npart 6");        
    #define OBJECT_NAME fake3
    #include "CCPolite_constructor.h"
    fake = (CPolite1 *)&fake3;
    fake->Thank();
    fake->Greet();

    puts("\npart 7");        
    #define OBJECT_NAME fake4
    #include "CCPolite_constructor.h"
    fake2 = (CPolite2 *)&fake4;
    fake2->Thank();
    fake2->Greet();    

    return 0;
}

الإخراج:

part 1
hello!
thank you!

part 2
hi!
ty!

part 3
ty!
hi!
thank you!
hello!

part 4
HI I AM C!!!!
THANK YOU, I AM C!!

part 5
THANK YOU, I AM C!!
HI I AM C!!!!

part 6
HI I AM C!!!!
THANK YOU, I AM C!!

part 7
THANK YOU, I AM C!!
HI I AM C!!!!

ملاحظة وبما أنني لا تخصيص بلدي وهمية وجوه لا تحتاج أن تفعل أي التدمير ؛ destructors يتم وضع تلقائيا في نهاية نطاق حيوي تخصيص الكائنات لاستعادة ذاكرة الحرفي الكائن نفسه vtable المؤشر.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top