لماذا يؤدي هذا المدمرة الافتراضية إلى أن يكون خارجيًا لم يتم حله؟

StackOverflow https://stackoverflow.com/questions/3560786

سؤال

النظر في ما يلي:

في 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 toVTable لـ x '/home/oyxdce/ccs7g3vl.o: في الوظيفة X::X()': prog.cpp:(.text+0x16): undefined reference toVTABLE لـ X 'Collect2: LD RETURS 1 حالة خروج

أشعر بالارتباك إذا كان التشخيص مطلوبًا حقًا من برنامج التحويل البرمجي لرمز البروتوكول الاختياري ، لذلك فكر في نشر هذا ، حتى مع المخاطرة بالهبوط :). بالطبع ، مترجم جيد يجب أن أعتقد.

قد تفلت من هذا لأن كل من التقييد والدمار خاصان - إذا لم يكن هناك أي مرجع آخر للصف العاشر في بنيتك ، فقد يستنتج المترجم أن الدمار غير مطلوب ، لذلك فإن عدم وجود تعريف ليس كبيرًا.

هذا لا يفسر لي لماذا تفشل الحالة 1 بينما 2 و 3 يبني موافق. أتساءل ماذا يحدث إذا تم نشر كلاهما؟

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