معنى - شرح polymorphism c++




تعدد الأشكال في ج++ (5)

بقدر ما أعلم:

يوفر C ++ ثلاثة أنواع مختلفة من تعدد الأشكال.

  • وظائف افتراضية
  • اسم الوظيفة الزائد
  • زيادة الحمولة من جانب المشغل

بالإضافة إلى الأنواع الثلاثة المذكورة أعلاه من تعدد الأشكال ، هناك أنواع أخرى من تعدد الأشكال:

  • وقت التشغيل
  • وقت الترجمة
  • تعدد الأشكال المخصص
  • بارامترايفي الشكل

وأنا أعلم أن تعدد الأشكال وقت التشغيل يمكن أن يتحقق عن طريق وظائف افتراضية ويمكن أن يتحقق تعدد الأشكال الثابت بواسطة وظائف القالب

لكن للاثنين الآخرين

  • تعدد الأشكال المخصص
  • تعدد الأشكال البارامترية يقول الموقع ،

تعدد الأشكال المخصص:

إذا كان نطاق الأنواع الفعلية التي يمكن استخدامها محدودًا ويجب أن يتم تحديد المجموعات بشكل فردي قبل الاستخدام ، فإن هذا يسمى تعدد أشكال ad-hoc.

تعدد الأشكال البارامترية:

إذا تمت كتابة جميع التعليمات البرمجية دون ذكر أي نوع معين ، وبالتالي يمكن استخدامها بشفافية مع أي عدد من الأنواع الجديدة ، يطلق عليها تعدد الأشكال parametric.

أنا بالكاد أفهمها :(

يمكن لأي شخص أن يفسر لهم كلاهما إذا كان ذلك ممكنا مع مثال؟ آمل أن تكون الإجابات على هذه الأسئلة مفيدة للعديد من المعاصرين الجدد من كلياتهم.


فهم / متطلبات لتعدد الأشكال

لفهم تعدد الأشكال - كما يستخدم المصطلح في علوم الحوسبة - يساعد على البدء من اختبار بسيط وتعريفه. يعتبر:

    Type1 x;
    Type2 y;

    f(x);
    f(y);

هنا ، f() هو تنفيذ بعض العمليات ويتم إعطاء القيم x و y كمدخلات.

لإظهار تعدد الأشكال ، يجب أن يكون f() قادراً على العمل بقيم من نوعين متميزين على الأقل (على سبيل المثال int و double ) ، وإيجاد وتنفيذ كود ملائم من النوع الملائم.

آليات C ++ لتعدد الأشكال

تعدد الأشكال صريح المحددة مبرمج

يمكنك كتابة f() بحيث يمكن أن تعمل على أنواع متعددة بأي من الطرق التالية:

  • تجهيزها:

    #define f(X) ((X) += 2)
    // (note: in real code, use a longer uppercase name for a macro!)
    
  • الحمولة الزائدة:

    void f(int& x)    { x += 2; }
    
    void f(double& x) { x += 2; }
    
  • قوالب:

    template <typename T>
    void f(T& x) { x += 2; }
    
  • إرسال افتراضي:

    struct Base { virtual Base& operator+=(int) = 0; };
    
    struct X : Base
    {
        X(int n) : n_(n) { }
        X& operator+=(int n) { n_ += n; return *this; }
        int n_;
    };
    
    struct Y : Base
    {
        Y(double n) : n_(n) { }
        Y& operator+=(int n) { n_ += n; return *this; }
        double n_;
    };
    
    void f(Base& x) { x += 2; } // run-time polymorphic dispatch
    

الآليات الأخرى ذات الصلة

تتم مناقشة تعدد الأشكال المقدمة من قبل المجمّع لأنواع مدمجة ، والتحويلات القياسية ، والصب / الإكراه لاحقًا من أجل:

  • انهم عادة ما يفهمون بشكل بديهي على أي حال (ما يبرر " أوه ، أن " رد فعل) ،
  • أنها تؤثر على عتبة في الطلب ، وسلاسة في استخدام ، والآليات المذكورة أعلاه ، و
  • التفسير هو تشتيت شديد من مفاهيم أكثر أهمية.

المصطلح

مزيد من التصنيف

بالنظر إلى الآليات متعددة الأشكال المذكورة أعلاه ، يمكننا تصنيفها بطرق مختلفة:

  • متى يتم تحديد الرمز الخاص بنوع متعدد الأشكال؟

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

  • ما هي الأنواع المدعومة؟

    • Ad-hoc بمعنى توفير شفرة صريحة لدعم كل نوع (مثل التحميل الزائد وتخصص القالب) ؛ أنت تضيف بوضوح دعم "لهذا" (حسب كل معنى ad hoc )، البعض الآخر "هذا"، وربما "that" أيضا ؛-).
    • حدودي بمعنى أنه يمكنك فقط محاولة استخدام الدالة لأنواع المعلمات المختلفة دون القيام بأي شيء على وجه التحديد لتمكين دعمها (مثل القوالب ، وحدات الماكرو). كائن مع وظائف / العوامل التي تتصرف مثل القالب / الماكرو يتوقع 1 هو كل هذا القالب / الماكرو يحتاج إلى القيام بعمله ، مع النوع الدقيق يجري غير ذي صلة. تساعد "المفاهيم" التي تم قطعها من C ++ 11 على التعبير عن مثل هذه التوقعات وفرضها - دعنا نأمل أن تجعلها في معيار لاحق.

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

        template <typename Duck>
        void do_ducky_stuff(const Duck& x) { x.walk().swim().quack(); }
        
        do_ducky_stuff(Vilified_Cygnet());
        
    • يسمح لك النوع الفرعي (التضمين التضمين) بالتعامل مع أنواع جديدة دون تحديث الخوارزمية / الوظيفة ، ولكن يجب أن تكون مشتقة من نفس الفئة الأساسية (إرسال افتراضي)

1 - قوالب مرنة للغاية. SFINAE (انظر أيضًا std::enable_if ) عدة مجموعات من التوقعات لتعدد الأشكال std::enable_if بشكل فعال. على سبيل المثال ، يمكنك ترميز ذلك عندما يكون نوع البيانات التي تقوم .size() عضوًا .size() ستستخدم وظيفة واحدة ، وإلا فإن وظيفة أخرى لا تحتاج إلى .size() (ولكن يفترض أنها تعاني بطريقة ما - على سبيل المثال ، باستخدام strlen() أو عدم الطباعة كرسالة مفيدة في السجل). يمكنك أيضًا تحديد سلوكيات مخصصة عندما يتم إنشاء القالب بمعلمات محددة ، إما ترك بعض المعلمات parametric ( تخصص قالب جزئي ) أو لا ( التخصص الكامل ).

"تعدد الأشكال"

ويوضح ألف شتاينباخ أنه في نموذج C ++ القياسي يشير فقط إلى تعدد الأشكال وقت التشغيل باستخدام الإرسال الظاهري. شركات عامة الخيال العلمي. يعني أكثر شمولا ، وفقا لمصمم C ++ منشئ Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ):

تعدد الأشكال - توفير واجهة واحدة للكيانات من أنواع مختلفة. توفر الوظائف الظاهرية تعدد أشكال ديناميكي (وقت التشغيل) من خلال واجهة مقدمة من فئة أساسية. توفر الوظائف والقوالب المحملة بشكل زائد تعدد أشكال ثابت (زمن تجميعي). TC ++ PL 12.2.6، 13.6.1، D & E 2.9.

هذه الإجابة - مثل السؤال - تتعلق بميزات C ++ إلى Comp. الخيال العلمي. المصطلح.

نقاش

مع معيار C ++ باستخدام تعريف أضيق "تعدد الأشكال" من شركات. الخيال العلمي. المجتمع ، لضمان الفهم المتبادل للجمهور النظر ...

  • باستخدام مصطلحات لا لبس فيها ("هل يمكننا جعل هذا الرمز قابل لإعادة الاستخدام لأنواع أخرى؟" أو "هل يمكننا استخدام إرسال ظاهري؟" بدلاً من "هل يمكن أن نجعل هذا الرمز متعدد الأشكال؟") ، و / أو
  • بوضوح تحديد المصطلحات الخاصة بك.

لا يزال ، ما هو حاسم ليكون مبرمج كبير C ++ هو فهم ما يفعله تعدد الأشكال حقا بالنسبة لك ...

مما يتيح لك كتابة رمز "خوارزمي" مرة واحدة ثم تطبيقه على العديد من أنواع البيانات

... ومن ثم يجب أن تدرك تمامًا كيف تتوافق آليات مختلفة الأشكال مع احتياجاتك الفعلية.

الدعاوى متعددة الأشكال وقت التشغيل:

  • المدخلات التي تمت معالجتها بطرق المصنع وبصقها باعتبارها مجموعة كائنات غير متجانسة تمت معالجتها عبر Base* s ،
  • يتم اختيار التنفيذ في وقت التشغيل استنادًا إلى ملفات التهيئة ومفاتيح سطر الأوامر وإعدادات واجهة المستخدم وما إلى ذلك ،
  • يختلف التنفيذ في وقت التشغيل ، على سبيل المثال لنمط جهاز الولاية.

عندما لا يكون هناك محرك واضح لتعدد الأشكال في وقت التشغيل ، فإن خيارات الترجمة المجمعة غالباً ما تكون أفضل. يعتبر:

  • يُعد التجميع - ما يسمى بالجوانب التي تم إعدادها في قوالب - أفضل من واجهات الدهون التي لا تعمل في وقت التشغيل
  • SFINAE
  • CRTP
  • تحسينات (بما في ذلك المداخلات وإزالة التعليمات البرمجية الميتة ، إلغاء التكرار ، صفائف ثابتة تستند إلى مكدس مقابل كومة الذاكرة المؤقتة)
  • __FILE__ ، __LINE__ ، سلسلة سلسلة حرفية والقدرات الفريدة الأخرى __FILE__ (التي تبقى شريرة ؛-))
  • يتم اعتماد الاستخدام الدلالي للقوالب ووحدات الماكرو ، ولكن لا تقيد بشكل مصطنع كيفية توفير هذا الدعم (نظرًا لأن الإرسال الظاهري يميل إلى المطالبة بتجاوزات وظيفة العضو المتطابقة تمامًا)

آليات أخرى تدعم تعدد الأشكال

كما هو موعود ، من أجل الاكتمال يتم تغطية العديد من الموضوعات الطرفية:

  • الحمولة الزائدة المقدمة المترجم
  • التحويلات
  • يلقي / الإكراه

وتختتم هذه الإجابة بنقاش حول كيفية دمج ما سبق لتفعيل وتبسيط الشفرة متعددة الأشكال - خاصة تعدد الأشكال البارامتري (القوالب ووحدات الماكرو).

آليات لرسم الخرائط لعمليات محددة النوع

> الحمولة الزائدة المترتبة على المترجم الضمني

من الناحية المفاهيمية ، يقوم المترجم بتحميل الكثير من المشغلين لأنواع مدمجة. لا يختلف مفهومًا عن التحميل الزائد المحدد من قِبل المستخدم ، ولكنه مدرج لأنه يمكن تجاهله بسهولة. على سبيل المثال ، يمكنك إضافتها إلى s int و s double باستخدام نفس التدوين x += 2 وينتج المترجم:

  • تعليمات وحدة المعالجة المركزية الخاصة بالنوع
  • نتيجة لنفس النوع.

يمتد التحميل الزائد بسلاسة إلى أنواع محددة من قبل المستخدم:

std::string x;
int y = 0;

x += 'c';
y += 'c';

تعتبر الأحمال الزائدة التي يوفرها المترجم للأنواع الأساسية شائعة في لغات الكمبيوتر عالية المستوى (3GL +) ، والنقاش الواضح لتعدد الأشكال يعني شيئًا أكثر. (2GLs - لغات التجميع - غالباً ما تتطلب من المبرمج أن يستخدم بشكل واضح مصطلحات تقوية الذاكرة المختلفة لأنواع مختلفة.)

> التحويلات القياسية

يصف القسم الرابع لـ C ++ Standard التحويلات القياسية.

تلخص النقطة الأولى بشكل جيد (من مسودة قديمة - نأمل أن تكون صحيحة بشكل جوهري):

-1- التحويلات القياسية هي تحويلات ضمنية محددة للأنواع المضمنة. تعدد العبارة conv مجموعة كاملة من هذه التحويلات. تسلسل التحويل القياسي عبارة عن سلسلة من التحويلات القياسية بالترتيب التالي:

  • صفر أو تحويل واحد من المجموعة التالية: تحويل من lvalue إلى rvalue وتحويل الصفوف إلى المؤشر وتحويل الدالة إلى المؤشر.

  • صفر أو تحويل واحد من المجموعة التالية: عروض ترويجية متكاملة ، ترويج النقاط العائمة ، تحويلات متكاملة ، تحويلات النقاط العائمة ، التحويلات العائمة المتكاملة ، تحويلات المؤشر ، مؤشر إلى تحويلات الأعضاء ، والتحويلات المنطقية.

  • صفر أو تحويل مؤهل واحد.

[ملاحظة: يمكن أن يكون تسلسل التحويل القياسي فارغًا ، أي أنه لا يمكن أن يتكون من أي تحويلات. ] سيتم تطبيق تسلسل تحويل قياسي على تعبير إذا لزم الأمر لتحويله إلى نوع وجهة مطلوب.

تسمح هذه التحويلات برمز مثل:

double a(double x) { return x + 2; }

a(3.14);
a(42);

تطبيق الاختبار السابق:

لكي تكون متعددة الأشكال ، يجب أن تكون [ a() ] قادرة على العمل بقيم نوعين متميزين على الأقل (مثل int ) ، وإيجاد وتنفيذ كود ملائم للنوع .

a() نفسها تدير رمز على وجه التحديد double ، وبالتالي فهي ليست متعددة الأشكال.

ولكن ، في المكالمة الثانية إلى a() يعرف المترجم أن يولد رمزًا مناسبًا للنوع من أجل "تعزيز نقطة عائمة" (المعيار § 4) لتحويل 42 إلى 42.0 . هذا الرمز الإضافي في وظيفة الاستدعاء . سنناقش أهمية هذا في الختام.

> الإكراه ، يلقي ، الصانعين الضمنية

تسمح هذه الآليات للفئات المعرفة من قِبل المستخدم بتحديد السلوكيات المشابهة للتحويلات القياسية لأنواع مدمجة. لنلقي نظرة:

int a, b;

if (std::cin >> a >> b)
    f(a, b);

هنا ، يتم تقييم الكائن std::cin في سياق منطقي ، بمساعدة مشغل التحويل. يمكن تجميع ذلك من الناحية المفاهيمية مع "عروض ترويجية متكاملة" وآخرون من التحويلات القياسية في الموضوع أعلاه.

يقوم المنشئون الضمنيون بفاعلية بنفس الشيء ، ولكن يتم التحكم بهم بواسطة نوع الإرسال إلى:

f(const std::string& x);
f("hello");  // invokes `std::string::string(const char*)`

الآثار المترتبة على المترجم المقدمة الزائد ، والتحويلات والإكراه

يعتبر:

void f()
{
    typedef int Amount;
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

إذا أردنا معالجة الكمية x كعدد حقيقي أثناء التقسيم (أي 6.5 بدلاً من تقريبه إلى 6) ، فنحن نحتاج فقط إلى التغيير إلى قيمة typedef double Amount .

هذا جيد ، لكن لم يكن الكثير من العمل لجعل الشفرة صريحة "اكتب الصحيح":

void f()                               void f()
{                                      {
    typedef int Amount;                    typedef double Amount;
    Amount x = 13;                         Amount x = 13.0;
    x /= 2;                                x /= 2.0;
    std::cout << double(x) * 1.1;          std::cout << x * 1.1;
}                                      }

ولكن ، ضع في اعتبارك أنه يمكننا تحويل الإصدار الأول إلى template :

template <typename Amount>
void f()
{
    Amount x = 13;
    x /= 2;
    std::cout << x * 1.1;
}

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

template <typename Amount, typename Policy>
void f()
{
    Amount x = Policy::thirteen;
    x /= static_cast<Amount>(2);
    std::cout << traits<Amount>::to_double(x) * 1.1;
}

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

  • "بعيدا" من أنواع المعلمات

    • من العديد من أنواع البيانات مقابض رمز خوارزمية متعددة الأشكال

    • إلى رمز مكتوب لعدد (من المحتمل أن يكون أقل) من الأنواع (نفسها أو غيرها).

  • "إلى" الأنواع البارامترية من قيم النوع الثابت

فهي لا تنشئ سياقات متعددة الأشكال من تلقاء نفسها ، ولكنها تساعد في تمكين / تبسيط الكود داخل مثل هذه السياقات.

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

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


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

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

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

http://en.wikipedia.org/wiki/Parametric_polymorphism


في C ++ ، التمييز المهم هو وقت التشغيل مقابل الربط وقت التجميع. لا تساعد ميزة parametric المخصصة في الحقيقة ، كما سأوضح لاحقًا.

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

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

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

تتيح لك القوالب تحديد الكثير من الأحمال الزائدة في الوظيفة دفعة واحدة.

هناك مجموعة أخرى من الأسماء لنفس فكرة وقت الاستبصار ...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

ترتبط هذه الأسماء بشكل أكبر بـ OOP ، لذلك من الغريب أن نقول أن القالب أو وظيفة أخرى غير الأعضاء تستخدم الربط المبكر.

لفهم العلاقة بين الوظائف الظاهرية والوظيفة الزائدة بشكل أفضل ، من المفيد أيضًا فهم الفرق بين "الإرسال المفرد" و "الإرسال المتعدد". يمكن فهم الفكرة على أنها تقدم ...

  • أولا ، هناك وظائف monomorphic. يتم تعريف تنفيذ الدالة بشكل فريد من خلال اسم الدالة. لا شيء من المعلمات هو خاص.
  • ثم هناك إرسال واحد. تعتبر إحدى المعلمات خاصة ، وتستخدم (مع الاسم) لتحديد أي تطبيق يتم استخدامه. في OOP ، نميل إلى التفكير في هذه المعلمة على أنها "الكائن" ، ثم سردها قبل اسم الدالة وما إلى ذلك.
  • ثم ، هناك إرسال متعددة. تساهم أي / جميع المعلمات في تحديد التطبيق الذي سيتم استخدامه. لذلك ، مرة أخرى ، لا تحتاج أي معلمة أن تكون خاصة.

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

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

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

يعني تعدد الأشكال البارامتيري أن لديك أنواع كمعلمات ، ويتم استخدام نفس الشفرة بالضبط بغض النظر عن النوع الذي تستخدمه لهذه المعلمات.

تعدد الأشكال الخاص هو مخصص بمعنى أنك توفر رمز مختلف اعتمادا على أنواع معينة.

الحمولة الزائدة والوظائف الافتراضية كلاهما أمثلة لتعدد الأشكال المخصص.

مرة أخرى ، هناك بعض المرادفات ...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

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

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

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

على سبيل المثال هاسكل ، يمكنك ...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

a هنا هو نوع متعدد الأشكال غير المقيد. يمكن أن يكون أي شيء ، لذلك ليس هناك الكثير يمكننا القيام به مع قيم من هذا النوع.

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

هنا ، يكون a مقيد ليكون عضوًا في فئة Num - وهي أنواع تتصرف مثل الأرقام. يتيح لك هذا القيد القيام بأشياء رقمية مع تلك القيم ، مثل إضافتها. حتى الرقم 3 هو متعدد الأشكال - من نوع الاستدلال من الأرقام التي تعني 3 من النوع a .

أفكر في هذا كما تعدد الأشكال parametric مقيد. هناك تطبيق واحد فقط ، ولكن لا يمكن تطبيقه إلا في حالات مقيدة. والجانب المخصص هو اختيار + و 3 لاستخدامه. كل "مثيل" من Num له تطبيق مميز خاص به. حتى في هاسكل "parametric" و "unconstrained" ليست مترادفات حقاً - لا تلومني ، ليس خطأي!

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

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

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


قد لا يكون هذا مفيدًا ، لكنني قمت بذلك لتقديم أصدقائي للبرمجة من خلال إعطاء وظائف محددة ، مثل START ، و END للوظيفة الرئيسية ، لذا لم تكن شاقة للغاية (استخدموا ملف main.cpp فقط). أنه يحتوي على طبقات متعددة الأشكال والبنى ، والقوالب ، وناقلات ، صفائف ، توجيهات preproccessor ، الصداقة ، والمشغلين والمؤشرات (كل ما يجب أن تعرفه قبل محاولة تعدد الأشكال):

ملاحظة: لم يتم الانتهاء من ذلك ، ولكن يمكنك الحصول على هذه الفكرة

MAIN.CPP

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

main.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};

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





c++-faq