c++ operator - لماذا تخفي وظيفة تجاوزها في الفئة المشتقة الأحمال الزائدة الأخرى للفئة الأساسية؟




overloading overloaded (5)

يكون إخفاء الاسم منطقيًا لأنه يمنع حدوث غموض في تحليل الاسم.

خذ بعين الاعتبار هذا الرمز:

class Base
{
public:
    void func (float x) { ... }
}

class Derived: public Base
{
public:
    void func (double x) { ... }
}

Derived dobj;

إذا كان Base::func(float) غير مخفي بواسطة Derived::func(double) في Derived ، فإننا ندعو إلى وظيفة الفئة الأساسية عند استدعاء dobj.func(0.f) ، على الرغم من أنه يمكن ترقية عوامة إلى مضاعفة .

المرجع: http://bastian.rieck.ru/blog/posts/2016/name_hiding_cxx/

النظر في الكود:

#include <stdio.h>

class Base {
public: 
    virtual void gogo(int a){
        printf(" Base :: gogo (int) \n");
    };

    virtual void gogo(int* a){
        printf(" Base :: gogo (int*) \n");
    };
};

class Derived : public Base{
public:
    virtual void gogo(int* a){
        printf(" Derived :: gogo (int*) \n");
    };
};

int main(){
    Derived obj;
    obj.gogo(7);
}

حصلت على هذا الخطأ:

>g++ -pedantic -Os test.cpp -o test
test.cpp: In function `int main()':
test.cpp:31: error: no matching function for call to `Derived::gogo(int)'
test.cpp:21: note: candidates are: virtual void Derived::gogo(int*) 
test.cpp:33:2: warning: no newline at end of file
>Exit code: 1

هنا ، تقوم دالة الفصل المشتق بفوق كل الوظائف ذات الاسم نفسه (وليس التوقيع) في الفئة الأساسية. بطريقة ما ، لا يبدو هذا السلوك من C ++ OK. لا متعدد الأشكال.


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

في هذه الحالة ، يتم العثور على gogo(int*) (لوحده) في نطاق الفئة المشتقة ، ولأنه لا يوجد تحويل قياسي من int إلى int * ، يفشل البحث.

الحل هو إدخال إعلانات القاعدة في تصريح استخدام في الفئة المشتقة:

using Base::gogo;

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


هذا هو "حسب التصميم". في دقة التحميل الزائد C ++ لهذا النوع من الأساليب يعمل كما يلي.

  • بدءًا من نوع المرجع ثم انتقل إلى النوع الأساسي ، ابحث عن النوع الأول الذي يحتوي على طريقة باسم "gogo"
  • النظر فقط في الأساليب التي تحمل اسم "gogo" في هذا النوع تجد زيادة في التحميل

وبما أن الدالة Derived لا تحتوي على دالة مطابقة تسمى "gogo" ، يفشل تحليل الحمل الزائد.


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

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

على سبيل المثال ، لنفترض أن الفئة B الأساسية تحتوي على دالة foo دالة تأخذ معلمة من type void * ، ويتم حل جميع المكالمات إلى foo(NULL) إلى B::foo(void *) . لنفترض أنه لا يوجد إخفاء للإسم وهذا B::foo(void *) مرئي في العديد من الطبقات المختلفة من B ومع ذلك ، دعنا نقول في بعض سلالة [غير مباشر ، بعيد] ( D من الفئة ( B foo(int) يتم تعريف الدالة foo(int) . الآن ، دون اسم يخفي D لديه كل من foo(void *) و foo(int) مرئية والمشاركة في دقة الزائد. ما هي الوظيفة التي سيتم بها حل مكالمات foo(NULL) ، في حالة إدخالها من خلال كائن من النوع D ؟ سوف يتم حلها إلى D::foo(int) ، نظرًا لأن int هي أفضل تطابق للصفر الكامل (أي NULL ) من أي نوع مؤشر. لذلك ، في جميع أنحاء التسلسل الهرمي يدعو إلى foo(NULL) إلى وظيفة واحدة ، بينما في D (وتحت) فجأة إلى آخر.

تم اعتبار هذا السلوك غير مرغوب فيه عندما تم تصميم اللغة. وكنهج أفضل ، تقرر اتباع مواصفات "إخفاء الاسم" ، وهذا يعني أن كل فئة تبدأ بـ "ورقة نظيفة" فيما يتعلق بكل اسم أسلوب تعلنه. ولتجاوز هذا السلوك ، يلزم إجراء صريح من المستخدم: وهو في الأصل إعادة تعريف للطريقة (الأساليب) الموروثة (التي تم إيقافها حاليًا) ، وهو الآن استخدام واضح للإعلان - الاستخدام.

كما لاحظت بشكل صحيح في مشاركتك الأصلية (أنا أشير إلى ملاحظة "غير متعددة الأشكال") ، قد ينظر إلى هذا السلوك على أنه انتهاك لعلاقة IS-A بين الطبقات. هذا صحيح ، ولكن على ما يبدو في ذلك الوقت ، تقرر أنه في نهاية المطاف سيكون إخفاء الاختباء أقل شرًا.


إذا كانت lambda عديمة الحالة ، أي ، [](...){...} ، يسمح C ++ 11 لها بالتدهور إلى مؤشر وظيفة. من الناحية النظرية ، سيكون مترجم C ++ 11 متوافقًا قادرًا على ترجمة هذا:

auto ignore = []() { return 10; };  //1 note misssing & in []!
std::vector<int (*)()> v;     //2
v.push_back([]() { return 100; });  //3




c++ polymorphism override