سؤال

في لغة C++، هل يمكن استخدام مؤشرات وظائف الأعضاء للإشارة إلى أعضاء الفئة المشتقة (أو حتى الأساسية)؟

يحرر:ربما سوف يساعد مثال.لنفترض أن لدينا تسلسل هرمي من ثلاث فئات X, Y, Z في ترتيب الميراث.Y وبالتالي لديه فئة أساسية X وفئة مشتقة Z.

الآن يمكننا تحديد مؤشر وظيفة العضو p للصف Y.هذا مكتوب على النحو التالي:

void (Y::*p)();

(للتبسيط، سأفترض أننا مهتمون فقط بالوظائف ذات التوقيع void f() )

هذا المؤشر p يمكن الآن استخدامها للإشارة إلى وظائف الأعضاء في الفصل Y.

هذا السؤال (سؤالان حقًا) هو:

  1. يستطيع p يمكن استخدامها للإشارة إلى دالة في الفئة المشتقة Z?
  2. يستطيع p يمكن استخدامها للإشارة إلى وظيفة في الفئة الأساسية X?
هل كانت مفيدة؟

المحلول

C++03 ستد، §4.11 2 مؤشر لتحويلات الأعضاء:

قيمة r من النوع "مؤشر إلى عضو من النوع B السيرة الذاتية T،" حيث B هو نوع فئة، يمكن تحويله إلى قيمة r من النوع "مؤشر إلى عضو من النوع D السيرة الذاتية T، حيث D هي فئة مشتقة (البند 10) من B.إذا كانت B فئة أساسية غير قابلة للوصول (البند 11)، أو غامضة (10.2) أو افتراضية (10.1) من D، فإن البرنامج الذي يستلزم هذا التحويل يكون سيئ التشكيل.تشير نتيجة التحويل إلى نفس العضو الذي كان يشير إليه المؤشر قبل إجراء التحويل، ولكنها تشير إلى عضو الفئة الأساسية كما لو كان عضوًا في الفئة المشتقة.تشير النتيجة إلى العضو في مثيل D لـ B.نظرًا لأن النتيجة تحتوي على نوع "مؤشر إلى عضو من النوع D". السيرة الذاتية T،" يمكن إلغاء الإشارة إليه باستخدام كائن D.والنتيجة هي نفسها كما لو تم إلغاء الإشارة إلى المؤشر إلى العضو B مع الكائن الفرعي B للكائن D.يتم تحويل قيمة مؤشر العضو الفارغ إلى قيمة مؤشر العضو الفارغ لنوع الوجهة. 52)

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

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

class A {
public: 
    void foo();
};
class B : public A {};
class C {
public:
    void bar();
};
class D {
public:
    void baz();
};
class E : public A, public B, private C, public virtual D {
public: 
    typedef void (E::*member)();
};
class F:public E {
public:
    void bam();
};
...
int main() {
   E::member mbr;
   mbr = &A::foo; // invalid: ambiguous; E's A or B's A?
   mbr = &C::bar; // invalid: C is private 
   mbr = &D::baz; // invalid: D is virtual
   mbr = &F::bam; // invalid: conversion isn't defined by the standard
   ...

التحويل في الاتجاه الآخر (عبر static_cast) يحكمها § 5.2.9 9:

قيمة r من النوع "مؤشر إلى عضو D من النوع السيرة الذاتية1 T" يمكن تحويله إلى قيمة r من النوع "مؤشر إلى عضو من النوع B السيرة الذاتية2 T"، حيث B هي فئة أساسية (بند 10 فئة مشتقة) من D، في حالة وجود تحويل قياسي صالح من "المؤشر إلى عضو B من النوع T" إلى "مؤشر إلى عضو D من النوع T" (4.11 التحويل)، و السيرة الذاتية2 هي نفس مؤهل السيرة الذاتية أو مؤهلات السيرة الذاتية أكبر منها، السيرة الذاتية1.11) قيمة مؤشر العضو الفارغ (4.11 التحويل) إلى قيمة مؤشر العضو الفارغة لنوع الوجهة.إذا كانت الفئة B تحتوي على العضو الأصلي، أو كانت فئة أساسية أو مشتقة من الفئة التي تحتوي على العضو الأصلي، فإن المؤشر الناتج إلى العضو يشير إلى العضو الأصلي.وإلا فإن نتيجة الإلقاء غير محددة.[ملحوظة:على الرغم من أن الفئة B لا تحتاج إلى أن تحتوي على العضو الأصلي، إلا أن النوع الديناميكي للكائن الذي تم إلغاء الإشارة إليه بالمؤشر إلى العضو يجب أن يحتوي على العضو الأصلي؛يرى 5.5 expr.mptr.oper.]

11) أنواع الوظائف (بما في ذلك تلك المستخدمة في أنواع وظائف الأعضاء) ليست مؤهلة أبدًا ؛يرى 8.3.5 dcl.fct.

باختصار، يمكنك التحويل من مشتق D::* إلى قاعدة B::* إذا كان بإمكانك التحويل من a B::* إلى أ D::*, ، على الرغم من أنه يمكنك فقط استخدام B::* على الكائنات من النوع D أو المنحدرة من D.

نصائح أخرى

لست متأكدًا بنسبة 100% مما تطلبه، ولكن إليك مثالًا يعمل مع الوظائف الافتراضية:

#include <iostream>
using namespace std;

class A { 
public:
    virtual void foo() { cout << "A::foo\n"; }
};
class B : public A {
public:
    virtual void foo() { cout << "B::foo\n"; }
};

int main()
{
    void (A::*bar)() = &A::foo;
    (A().*bar)();
    (B().*bar)();
    return 0;
}

المشكلة الحاسمة فيما يتعلق بالمؤشرات للأعضاء هي أنه يمكن تطبيقها على أي مرجع أو مؤشر لفئة من النوع الصحيح.وهذا يعني أنه بسبب Z مشتق من Y مؤشر (أو مرجع) من نوع المؤشر (أو المرجع) إلى Y قد يشير في الواقع (أو يشير) إلى الكائن الفرعي للفئة الأساسية لـ Z أو أي فئة أخرى مستمدة من Y.

void (Y::*p)() = &Z::z_fn; // illegal

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

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

void (Y::*p)() = &Y::y_fn;
void (Z::*q)() = p; // legal and safe

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

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

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

  1. لقد نجح هذا، ولكنه يتطلب طاقمًا عند تعيين وظيفة العضو المشتقة إليه p.
  2. وقد نجح هذا أيضًا، ولكنه تطلب المزيد من القوالب عند إلغاء الإسناد p.

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

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

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

لنفترض أن لدينا class X, class Y : public X, and class Z : public Y

يجب أن تكون قادرًا على تعيين طرق لكل من X وY لمؤشرات من النوع void (Y::*p)() ولكن ليس طرقًا لـ Z.لمعرفة السبب خذ بعين الاعتبار ما يلي:

void (Y::*p)() = &Z::func; // we pretend this is legal
Y * y = new Y; // clearly legal
(y->*p)(); // okay, follows the rules, but what would this mean?

من خلال السماح بهذا التعيين، فإننا نسمح باستدعاء طريقة لـ Z على كائن Y والتي يمكن أن تؤدي إلى من يعرف ماذا.يمكنك جعل كل شيء يعمل عن طريق إلقاء المؤشرات ولكن هذا ليس آمنًا أو مضمونًا للعمل.

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

#include <iostream>
#include <string>

using namespace std;

class A {
public:
    virtual void traverse(string arg) {
        find(&A::visit, arg);
    }

protected:
    virtual void find(void (A::*method)(string arg),  string arg) {
        (this->*method)(arg);
    }

    virtual void visit(string arg) {
        cout << "A::visit, arg:" << arg << endl;
    }
};

class B : public A {
protected:
    virtual void visit(string arg) {
        cout << "B::visit, arg:" << arg << endl;
    }
};

int main()
{
    A a;
    B b;
    a.traverse("one");
    b.traverse("two");
    return 0;
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top