لماذا يؤدي هذا المدمرة الافتراضية إلى أن يكون خارجيًا لم يتم حله؟
-
01-10-2019 - |
سؤال
النظر في ما يلي:
في XH:
class X
{
X();
virtual ~X();
};
X.CPP:
#include "X.h"
X::X()
{}
حاول إنشاء هذا (أنا أستخدم هدف .DLL لتجنب وجود خطأ في Main Main ، وأنا أستخدم Visual Studio 2010):
خطأ 1 خطأ LNK2001: الرمز الخارجي غير المحلول "الخاص: افتراضي __thiscall x :: ~ x (void)" (؟؟ 1x eae@xz)
التعديلات الصغيرة تؤدي إلى بناء ناجح ، ولكن:
XH:
class X
{
inline X(); // Now inlined, and everything builds
virtual ~X();
};
أو
XH:
class X
{
X();
~X(); // No longer virtual, and everything builds
};
ما الذي يسبب الخارجي الذي لم يتم حله في الرابط عندما يكون .dtor افتراضيًا أو عندما لا يكون.
تعديل:
أو ربما أكثر إثارة للاهتمام ، لماذا أنا ليس احصل على خارجي لم يتم حله إذا جعلت المدمر غير قذر ، أو إذا قمت بتدوين المُنشئ؟
المحلول
الوضع 1:
لديك رمز المُنشئ.
لذلك يقوم بإنشاء المنشئ في ملف الكائن. يحتاج المُنشئ إلى عنوان المدمر لوضعه في الطاولة الافتراضية لأنه لا يمكن العثور عليه ، لا يمكن بناء المُنشئ.
الموقف 2: (مُنشئ مضمّن)
يقرر المترجم أنه لا يحتاج إلى بناء المُنشئ (كما سيتم ضمه).
على هذا النحو ، لا يزرع أي رمز وبالتالي لا يحتاج إلى عنوان المدمر.
إذا قمت بتأسيس كائن من النوع X ، فسيشتكي مرة أخرى.
الوضع 3: (المدمر غير الافتراضي)
لا تحتاج إلى عنوان المدمر لبناء المنشئ.
لذلك لا يشكو.
سوف يشكو إذا قمت بتسهيل كائن من النوع X.
نصائح أخرى
تحتاج إلى إعطاء جسم للمدمر الافتراضي:
class X
{
X();
virtual ~X() {}
};
في C ++ ، يجب أن تكون وظائف مُعرف إذا وفقط إذا كانوا تستخدم في برنامجك (انظر ODR في 3.2/2). بشكل عام ، الوظائف غير الذروة تستخدم إذا تم استدعاؤها من التعبيرات التي يحتمل أن يتم تقييمها. تعتبر أي وظيفة افتراضية غير شرطية دون قيد أو شرط تستخدم. عندما تكون وظائف الأعضاء الخاصة [غير الذروة تستخدم تم تعريفه في مواقع مخصصة لمعايير اللغة. وهلم جرا.
في الخاص بك أول على سبيل المثال ، أعلنت المدمر الخاص بك كدالة افتراضية غير مرئية. هذا يعني على الفور أن مدمرك هو تستخدم في برنامجك. هذا ، بدوره ، يعني أن تعريف من هذا المدمر مطلوب. لقد فشلت في تقديم تعريف ، لذلك أبلغ المترجم عن خطأ.
في الخاص بك الثالث مثال المدمر غير قذر. بما أنك لست كذلك استخدام المدمر في البرنامج الخاص بك ، لا يوجد تعريف مطلوب وتجميع الكود (انظر 12.4 للحصول على وصف مفصل لما يشكل أ استعمال من المدمر).
في الخاص بك ثانيا على سبيل المثال ، تتعامل مع مجموعة من التنفيذ ، تسبب في حقيقة أن المُنشئ مضمون. نظرًا لأن المدمرة غير افتراضية ، فإن التعريف مطلوب. ومع ذلك ، فشل المترجم الخاص بك في اكتشاف الخطأ ، وهذا هو السبب في أن الكود يبدو أنه يجمع بنجاح. يمكنك الحفر لأسباب هذا السلوك في تفاصيل التنفيذ ، ولكن من وجهة نظر C ++ ، فإن هذا المثال مكسور مثل الأول لنفس السبب بالضبط.
إجابة سؤالك الأول ،
ما الذي يسبب الخارجي الذي لم يتم حله في الرابط عندما يكون .dtor افتراضيًا أو عندما لا يكون.
... هو ، بكل بساطة ، أنه ليس لديك تعريف للمدمر.
الآن سؤالك الثاني أكثر إثارة للاهتمام إلى حد ما:
لماذا لا أحصل على خارجي لم يتم حله إذا جعلت المدمر غير قذر ، أو إذا قمت بتدوين المنشئ؟
والسبب هو أن المترجم الخاص بك لم يكن بحاجة X
المدمر لأنك لم تنشأ أبدًا X
, ، لذلك ألقى صفك بأكمله بعيدا. إذا حاولت تجميع هذا البرنامج ، فستحصل على خارجي لم يتم حله:
class X
{
public:
X();
~X();
};
X::X() {};
int main()
{
X x;
return 0;
}
ولكن إذا علقت X x;
سيتم تجميعها على ما يرام ، كما لاحظت.
الآن دعنا نعود إلى سبب عدم تجميعها إذا كان المدمر إذا virtual
. أنا أتكهن هنا ، لكنني أعتقد أن السبب هو أنه نظرًا لأن لديك مدمرة افتراضية ، X
هو الآن فئة متعددة الأشكال. من أجل تخطيط فئات متعددة الأشكال في الذاكرة ، فإن المترجمين الذين يقومون بتنفيذ تعدد الأشكال باستخدام أ vtable تحتاج إلى العزف على كل وظيفة افتراضية. لم تنفذ X::~X
, ، لذلك نتائج خارجية لم تحل.
لماذا لا يرمي المترجم فقط X
بعيدا كما فعلت عندما X
لم تكن فئة متعددة الأشكال؟ المزيد من التكهنات هنا. لكنني أتوقع أن السبب هو أنه حتى لو لم تكن قد قمت بتثبيته مباشرة X
, ، لا يمكن أن يكون متأكدا من عدم وجود أي مكان في الكود الخاص بك X
عش ، masqerading كشيء آخر. على سبيل المثال ، فكر في فئة قاعدة مجردة. في هذه الحالة ، لن تنشئ أبدًا Base
مباشرة والرمز ل Derived
قد يكون في وحدة ترجمة منفصلة تماما. لذلك عندما يصل المترجم إلى هذه الفئة متعددة الأشكال ، فإنه لا يمكنه تجاهله حتى لو لم يكن يعرف أنك قمت بتأسيسه.
هذا ليس برنامجًا كاملاً حتى الآن (أو حتى DLL كاملة). عندما تحصل على الخطأ ، يتم مساعدتك بالفعل ، لأن X غير قابل للاستعمال بدون تعريف لـ ~ x ()
كل ما يعنيه هو أن مثيل البرمجي المحدد هذا يحتاج إلى تعريف له في بعض الحالات. حتى لو كان يجمع ، فإنه لا يفعل أي شيء.
لديّ شكوك في هذا السلوك المحدد للتنفيذ. هذا هو السبب
$ 10.3/8- "يجب تحديد وظيفة افتراضية معلنة في الفصل ، أو إعلان نقي (10.4) في تلك الفئة ، أو كليهما ؛ ولكن لا يلزم التشخيص (3.2)."
تعطي GCC خطأ مثل أدناه ، والتي مرة أخرى ، هي موحية للغاية (بالنسبة لي على الأقل) حول تفاصيل التنفيذ غير القياسية لتنفيذ الوظائف الافتراضية
/home/oyxdce/ccs7g3vl.o: في الوظيفة
X::X()': prog.cpp:(.text+0x6): undefined reference to
VTable لـ x '/home/oyxdce/ccs7g3vl.o: في الوظيفةX::X()': prog.cpp:(.text+0x16): undefined reference to
VTABLE لـ X 'Collect2: LD RETURS 1 حالة خروج
أشعر بالارتباك إذا كان التشخيص مطلوبًا حقًا من برنامج التحويل البرمجي لرمز البروتوكول الاختياري ، لذلك فكر في نشر هذا ، حتى مع المخاطرة بالهبوط :). بالطبع ، مترجم جيد يجب أن أعتقد.
قد تفلت من هذا لأن كل من التقييد والدمار خاصان - إذا لم يكن هناك أي مرجع آخر للصف العاشر في بنيتك ، فقد يستنتج المترجم أن الدمار غير مطلوب ، لذلك فإن عدم وجود تعريف ليس كبيرًا.
هذا لا يفسر لي لماذا تفشل الحالة 1 بينما 2 و 3 يبني موافق. أتساءل ماذا يحدث إذا تم نشر كلاهما؟