C ++ Указатели на наследование функций-членов - PullRequest
7 голосов
/ 14 марта 2009

Мне нужно, чтобы суперкласс выполнял обратные вызовы, определенные классом, который наследовал от него. Я относительно новичок в C ++, и из того, что я могу сказать, похоже, что тема указателей на функции-члена является очень темной областью.

Я видел ответы на вопросы и случайные посты в блогах, в которых обсуждаются всевозможные вещи, но я не уверен, что кто-то из них конкретно занимается моим вопросом здесь.

Вот простой кусок кода, который иллюстрирует то, что я пытаюсь сделать. Пример может не иметь большого смысла, но он точно напоминает код, который я пытаюсь написать.

class A {
    protected:
    void doSomething(void (A::*someCallback)(int a)) {
        (*this.*someCallback)(1234);
    }
};

class B : public A {
    public:
    void runDoIt() { doSomething(&B::doIt); }
    void runDoSomethingElse() { doSomething(&B::doSomethingElse); }
    protected:
    void doIt(int foo) {
        cout << "Do It! [" << foo << "]\n";
    }
    void doSomethingElse(int foo) {
        cout << "Do Something Else! [" << foo << "]\n";
    }
};

int main(int argc, char *argv[]) {
    B b;
    b.runDoIt();
    b.runDoSomethingElse();
}

Ответы [ 4 ]

8 голосов
/ 14 марта 2009

Проблема в том, что функция-член B не является функцией-членом A, даже если B происходит от A. Если у вас есть void (A::*)(), вы можете вызвать его на любом A *, независимо от фактического производного типа объекта, на который указывает.

(Конечно, тот же принцип применим и к A &.)

Предположим, B происходит от A, а C происходит от A; если бы можно было считать void (B::*)() (скажем) void (A::*)(), можно было бы сделать что-то вроде этого:

A *c=new C;
A *b=new B;
void (A::*f)()=&B::fn;//fn is not defined in A
(c->*f)();

И функция-член B будет вызываться для объекта типа C. Результаты будут в лучшем случае непредсказуемыми.

Исходя из примера кода и предполагая, что не используется что-то вроде boost, я склонен структурировать обратный вызов как объект:

class Callback {
public:
    virtual ~Callback() {
    }

    virtual Do(int a)=0;
};

Тогда функция, вызывающая обратный вызов, каким-то образом получает один из этих объектов, а не простой указатель на функцию:

class A {
protected:
    void doSomething(Callback *c) {
        c->Do(1234);
    }
};

Тогда вы могли бы иметь один обратный вызов для каждой производной функции, которую вы хотите вызвать. Например, для:

class B:public A {
public:
    void runDoIt() {
        DoItCallback cb(this);
        this->doSomething(&cb);
    }
protected:
    void doIt(int foo) {
        // whatever
    }
private:
    class DoItCallback:public Callback {
    public:
        DoItCallback(B *b):b_(b) {}

        void Do(int a) {
            b_->doIt(a);
        }
    private:
        B *b_;
    };
};

Очевидный способ урезать шаблон - поместить указатель на функцию-член в обратный вызов, поскольку производный обратный вызов может свободно работать с объектами определенного типа. Это сделало бы обратный вызов немного более общим, так как при обратном вызове он вызывал бы произвольную функцию-член для объекта типа B:

class BCallback:public Callback {
public:
    BCallback(B *obj,void (B::*fn)(int)):obj_(obj),fn_(fn) {}

    void Do(int a) {
        (obj_->*fn_)(a);
    }
private:
    B *obj_;
    void (B::*fn_)(int);
};

Это сделало бы так:

void B::runDoIt() {
    BCallback cb(this,&B::doIt);
    this->doSomething(&cb);
}

Это потенциально может быть «улучшено», хотя не все читатели могут увидеть его таким образом, если его шаблонировать:

template<class T>
class GenericCallback:public Callback {
public:
    GenericCallback(T *obj,void (T::*fn)(int)):obj_(obj),fn_(fn) {}

    void Do(int a) {
        (obj_->*fn_)(a);
    }
private:
    T *obj_;
    void (T::*fn_)(int);
};

Используя это, приведенная выше функция runDoIt может стать:

void B::runDoIt() {
    GenericCallback<B> cb(this,&B::doIt);
    this->doSomething(&cb);
}

(Общий обратный вызов также может быть создан на основе указателя на функцию-член, хотя в большинстве случаев это вряд ли обеспечит какое-либо практическое преимущество. Это просто больше ввода).

Я нашел способ структурирования таким образом, чтобы получилось хорошо, так как он не требует наследования. Поэтому он накладывает несколько ограничений на код, который должен вызываться обратно, что, на мой взгляд, всегда хорошо, и я обнаружил, что это перевешивает многословие, которое трудно полностью исключить. Невозможно, основываясь на примере, сказать, подойдет ли этот подход, хотя ...

7 голосов
/ 14 марта 2009

Если вы можете использовать библиотеки boost, я бы посоветовал вам использовать boost :: function для поставленной задачи.

class A {
public:
   void doSomething( boost::function< void ( int ) > callback )
   {
      callback( 5 );
   }
};

Тогда любой наследующий (или внешний класс) может использовать boost :: bind, который делает вызов:

class B {
public:
   void my_method( int a );
};
void test()
{
   B b;
   A a;
   a.doSomething( boost::bind( &B::my_method, &b, _1 ) );
};

Я не проверил точный синтаксис и набрал его на макушке, но это, по крайней мере, близко к правильному коду.

2 голосов
/ 14 марта 2009

C ++ предоставляет лучшие конструкции, чем указатели на функции-члены для ваших целей. (Вообще говоря, вам не нужно использовать указатели функций в правильно написанном чистом C ++). Два варианта от макушки головы:

1) Создайте в A чисто виртуальную функцию с именем doIt (). Вызовите doIt () из doSomething (). На B сохраните переменную-член enum, чтобы указать, что вы хотите сделать, и установите ее перед вызовом doSomething (). Переопределите doIt () в B, с его реализацией переключателем / регистром перечисления члена на базовый код, который вы хотите запустить.

2) Создайте класс DoInterface с помощью одного чисто виртуального метода doIt (). Создайте производные этого для вашего класса B, у которых есть указатель на экземпляр-владелец B, и реализуйте doIt (), вызывая соответствующую функцию на B в каждой из ваших реализаций DoInterface. doSomething () будет принимать экземпляр DoInterface в качестве параметра. Это называется шаблоном объекта обратного вызова.

0 голосов
/ 14 марта 2009

Вы пытаетесь вызвать метод, который определен в производном классе как базовый класс метода.
Но эта функция не определена в базовом классе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...