سؤال

لماذا C ++ ليس لديك منشئ افتراضي؟

لا يوجد حل صحيح

نصائح أخرى

اسمعها من فم الحصان :).

من Bjarne Stroustrup's C ++ Style and Technique الأسئلة الشائعةلماذا لا يوجد لدينا منشئين افتراضي؟

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

يستمر إدخال الأسئلة الشائعة لإعطاء الرمز للحصول على وسيلة لتحقيق هذه الغاية دون منشئ افتراضي.

وظائف افتراضية توفر أساسا السلوك متعدد الألوان. وهذا هو، عندما تعمل مع كائن نوع ديناميكي مختلف عن نوع ثابت (وقت الترجمة) الذي يشار إليه، يوفر السلوك المناسب ل فعلي نوع الكائن بدلا من النوع الثابت للكائن.

حاول الآن تطبيق هذا النوع من السلوك إلى منشئ. عند إنشاء كائن، يكون النوع الثابت دائما نفس نوع الكائن الفعلي منذ:

لبناء كائن، يحتاج المنشئ إلى النوع الدقيق للكائن هو إنشاء [...] علاوة على ذلك [...] لا يمكنك الحصول على مؤشر إلى منشئ

(Bjarne Stroustup (P424 لغة البرمجة C ++ SE))

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

هذا يناسب "أنت لا تدفع مقابل ما لا تستخدمه" فلسفة، على الرغم من أن كل مشروع كبير C ++ رأيته قد انتهى الأمر بتنفيذ شكل من أشكال مصنع مجردة أو انعكاس.

سببين يمكنني التفكير في:

السبب الفني

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

سبب المنطق

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

نحن نفعل، ليس مجرد منشئ :-)

struct A {
  virtual ~A() {}
  virtual A * Clone() { return new A; }
};

struct B : public A {
  virtual A * Clone() { return new B; }
};

int main() {

   A * a1 = new B;
   A * a2 = a1->Clone();    // virtual construction
   delete a2;
   delete a1;
}

لأسباب دلالية جانبا، لا يوجد VTable إلا بعد إنشاء الكائن، مما يجعل التعيين الظاهري عديم الفائدة.

ملخص: معيار C ++ استطاع حدد تدوين وسلوك ل "المنشئ الظاهري" هذا بديهية بشكل معقول وليس من الصعب جدا على المترجمين الدعم، ولكن لماذا يؤدي إلى تغيير قياسي لهذا على وجه التحديد عند وظائف يمكن بالفعل تنفيذها نظيفة باستخدام create() / clone() (انظر أدناه)؟ انها ليست مفيدة تقريبا مثل العديد من اقتراح اللغات الأخرى في خط الأنابيب.

مناقشة

دعونا نفترض آلية "منشئ افتراضي":

Base* p = new Derived(...);
Base* p2 = new p->Base();  // possible syntax???

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

الخط الثاني يفترض التدوين new p->Base() لطلب تخصيص ديناميكي والتشييد الافتراضي للآخر Derived هدف.

ملاحظات:

  • يجب أن يقوم برنامج التحويل البرمجي بتنظيم تخصيص الذاكرة قبل الاتصال بالبناء - منشئون يدعمون عادة التلقائي (بشكل غير رسمي "مكدس") تخصيص، ثابتة (لنطاق مساحة الاسم / الاسمstatic الكائنات)، و متحرك (بشكل غير رسمي "كومة") متى new يستخدم

    • حجم الكائن الذي سيتم بناؤه بواسطة p->Base() لا يمكن أن تكون معروفة عموما في وقت الترجمة، لذلك التخصيص الديناميكي هو النهج الوحيد الذي منطقي

  • لتخصيص ديناميكي ذلك يجب إرجاع مؤشر حتى يمكن أن يكون الذاكرة deleteد لاحقا.

  • يسرد التدوين المفترض صراحة new للتأكيد على التخصيص الديناميكي ونوع نتيجة المؤشر.

سيحتاج المحول البرمجي إلى:

  • معرفة مقدار الذاكرة Derived اللازمة، إما عن طريق استدعاء ضمنية virtual sizeof وظيفة أو وجود هذه المعلومات المتاحة عبر RTTI
  • يتصل operator new(size_t) لتخصيص الذاكرة
  • يستحضر Derived() مع التنسيب new.

أو

  • قم بإنشاء إدخال إضافي VTable لوظيفة تجمع بين التخصيص الديناميكي والبناء

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


`استنساخ ()` و "إنشاء ()`

ال C ++ FAQ المستندات "منشئ افتراضي" IDIOM, ، تحتوي virtual create() و clone() الأساليب إلى الإنشاء الافتراضي أو نسخ إنشاء كائن جديد مخصص ديناميكيا:

class Shape {
  public:
    virtual ~Shape() { } // A virtual destructor
    virtual void draw() = 0; // A pure virtual function
    virtual void move() = 0;
    // ...
    virtual Shape* clone() const = 0; // Uses the copy constructor
    virtual Shape* create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
  public:
    Circle* clone() const; // Covariant Return Types; see below
    Circle* create() const; // Covariant Return Types; see below
    // ...
};
Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }

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

يمكنك العثور على مثال والسبب الفني في سبب عدم السماح به في إجابة MeTefan. الآن إجابة منطقية على هذا السؤال وفقا لي هي:

الاستخدام الرئيسي للكلمة الأساسية الافتراضية هو تمكين السلوك متعدد الأشكال عندما لا نعرف نوع الكائن الذي سيجور مؤشر الفئة الأساسية إليه.

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

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

الآن، فكر في الوضع في الوقت الحالي عندما يتم تعيين كائن الفئة المراد توجيه بعض الذاكرة -> سيتم استدعاء منشئها تلقائيا في هذه الحالة نفسها!

لذلك يمكننا أن نرى أننا لا نحتاج فعليا إلى القلق بشأن كون المنشئ الظاهري، لأنه في أي من الحالات التي ترغب في استخدام سلوك متعدد الأشكال، كان قد تم تنفيذ منشئنا بالفعل جعل كائننا جاهزا للاستخدام!

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

نمط تصميم "طريقة المصنع" GOF يستخدم استخدام "مفهوم" المنشئ الظاهري، وهو حق في حالات تصميم معينة.

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

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

لذلك، إذا حاولنا إعلان مترجم المنشئ الظاهري رمي خطأ:

لا يمكن الإعلان عن المنشئين الظاهري

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

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

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

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

أخيرا، كما أشار شخص آخر، يمكنك تنفيذ نوع من المنشئ الظاهري باستخدام وظائف نوع "إنشاء" ثابت أو "INIT" التي تفعل الشيء نفسه في الأساس نفس الشيء الذي يقوم به منشئ افتراضي.

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

يجب أن لا تتصل بوظيفة الظاهرية داخل منشئك أيضا. يرى : http://www.artima.com/cppsource/nevercall.html.

بالإضافة إلى ذلك، لست متأكدا من أنك حقا بحاجة إلى منشئ افتراضي. يمكنك تحقيق بناء متعدد الألوان دون ذلك: يمكنك كتابة دالة تقوم ببناء كائنك وفقا للمعلمات اللازمة.

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

لا أستطيع أن أقول ببساطة مثل .. لا يمكننا أن نرث المصانعين. لذلك لا توجد نقطة تعلن منها الظاهري لأن الظاهري يوفر تعدد الأشكال.

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

هناك سبب أساسي للغاية: منشئون وظائف ثابتة فعالة، وفي C ++ لا توجد وظيفة ثابتة يمكن أن تكون افتراضية.

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

الآن، ما هو وظيفة البناء؟ في الاسم - يقوم منشئ "T" بتهيئة كائنات T كما يتم تخصيصها. هذا يحول تلقائيا كونه وظيفة عضو! يجب أن يكون كائن موجودا قبل أن يكون له مؤشر "هذا" وبالتالي VTable. هذا يعني أنه حتى لو عولجت اللغة المعالجة كوظائف عادية (لا، لأسباب ذات صلة لن أتعرض لها)، يجب أن تكون وظائف عضوا ثابتة.

طريقة رائعة لرؤية هذا هو البحث في نمط "المصنع"، وخاصة وظائف المصنع. يفعلون ما أنت بعد، وستلاحظ أنه إذا كان الفئة ر لديه طريقة المصنع، فهي ثابتة دائما. يجب أن يكون.

C ++ المنشئ الظاهري غير ممكن. على سبيل المثال، لا يمكنك وضع علامة منشئ Virtual.try هذا الرمز

#include<iostream.h>
using namespace std;
class aClass
{
    public:
        virtual aClass()
        {   
        }  
};
int main()
{
    aClass a; 
}

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

#include<iostream.h>
using namespace std;
class aClass
{
    public:
        aClass()
        {
            cout<<"aClass contructor\n";
        }
        ~aClass()
        {
            cout<<"aClass destructor\n";
        }

};
class anotherClass:public aClass
{

    public:
        anotherClass()
        {
            cout<<"anotherClass Constructor\n";
        }
        ~anotherClass()
        {
            cout<<"anotherClass destructor\n";
        }

};
int main()
{
    aClass* a;
    a=new anotherClass;
    delete a;   
    getchar(); 
}

في Main. a=new anotherClass; يخصص ذاكرة ل anotherClass في مؤشر a أعلن بالنوع من aClassهذا يسبب كل من المنشئ (في aClass و anotherClass) للاتصال تلقائيا. لذلك لا نحتاج إلى وضع علامة على المنشئ بأنه افتراضي. عند إنشاء كائن يجب عليه اتباع سلسلة الإبداع (أي أولا القاعدة ثم الفئات المشتقة). ولكن عندما نحاول حذف delete a; يسبب ذلك الاتصال فقط Destructor.So علينا التعامل مع المدمر باستخدام الكلمة الأساسية الافتراضية. لذا المنشئ الظاهري غير ممكن ولكن المدمر الظاهري هو.شكرا

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

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

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

    Virtual الكلمة الأساسية المرتبطة وظيفة تعني وظيفة نوع كائن معين سوف يتم استدعاؤه.

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

    على الرغم من أن التنفيذ الداخلي لن يسمح ببناء افتراضي لأسباب VPTR و VTable ذات صلة.


  1. سبب آخر هو أن C ++ هي لغة مكتوبة ثابتة ونحن بحاجة إلى معرفة نوع المتغير في وقت الترجمة.

    يجب أن يكون التحويل البرمجي على دراية بنوع الفصل لإنشاء الكائن. يعد نوع الكائن المراد إنشاؤه هو قرار تجميع الزمن.

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

    وبالتالي، لن يتم استدعاء المنشئ دون معرفة نوع الكائن في وقت الترجمة. وهكذا فشلت فكرة جعل منشئ افتراضي.

يتم إنشاء Vointer في وقت إنشاء الكائنات. VPointer Wont موجود قبل إنشاء الكائنات. لذلك ليس هناك أي نقطة مما جعل المنشئ الظاهري.

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