Подтвердить что ты не робот

Наследование наследования С++ и указатели функций-членов

В С++ можно использовать указатели функций-членов, чтобы указывать на производные (или даже базовые) члены класса?

EDIT: Возможно, пример поможет. Предположим, что мы имеем иерархию из трех классов X, Y, Z в порядке наследования. Y поэтому имеет базовый класс X и производный класс Z.

Теперь мы можем определить указатель на функцию-член p для класса Y. Это записывается как:

void (Y::*p)();

(Для простоты я предполагаю, что нас интересуют только функции с сигнатурой void f())

Этот указатель p теперь можно использовать для указания на функции-члены класса Y.

Этот вопрос (на самом деле, два вопроса):

  • Можно использовать p для указания функции в производном классе Z?
  • Можно использовать p для указания функции в базовом классе X?
4b9b3361

Ответ 1

С++ 03 std, §4.11 2 Указатель на преобразования членов:

Указатель типа r для члена B типа cv T ", где B - тип класса, может быть преобразован в rvalue типа" указатель на член D типа cv T ", где D - производный класс (раздел 10) B. Если B является недоступным (статья 11), двусмысленным (10.2) или виртуальным (10.1) базовым классом D, программа, которая требует, чтобы это преобразование плохо сформировалось. Результат преобразования относится к тому же члену, что и указатель на член перед преобразованием, но он относится к члену базового класса, как если бы он был членом производного класса. Результат ссылается на член в экземпляре Ds из B. Поскольку в результате имеет тип" указатель на член D типа cv T", он может быть разыменован объектом D. Результат такой же, как если бы указатель на член B был разыменован субъектом B. D. Значение указателя нулевого элемента преобразуется в значение указателя нулевого элемента для типа назначения. 52)

52) Правило для преобразования указателей на членов (от указателя к члену базы до указателя на член производного) выглядит инвертированным по сравнению с правилом для указателей на объекты (от указателя к производному до указатель на базу) (4.10, раздел 10). Эта инверсия необходима для обеспечения безопасности типа. Обратите внимание, что указатель на элемент не является указателем на объект или указателем на функцию, а правила для преобразования таких указателей не применяются к указателям на члены. В частности, указатель на член не может быть преобразован в void *.

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

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 A or B 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 типа cv1 T" может быть преобразовано в rvalue типа "указатель на элемент B типа cv2 T", где B - базовый класс (предложение 10 class.derived) D, если действительное стандартное преобразование из "указателя на член B of существует тип T" до "указателя на член D типа T" (4.11 conv.mem) и cv2 является той же самой cv-квалификацией, что и более высокая cv-квалификация, чем cv1. 11) Значение указателя нулевого элемента (4.11 conv.mem) преобразуется в значение указателя нулевого элемента для целевого типа. Если класс B содержит исходный элемент или является базовым или производным классом класса, содержащего исходный элемент, результирующий указатель на элемент указывает на исходный элемент. В противном случае результат приведения undefined. [Примечание: хотя класс B не должен содержать исходный элемент, динамический тип объекта, на котором указатель на элемент разыменован, должен содержать исходный элемент; см. 5.5 expr.mptr.oper.]

11) Типы функций (включая те, которые используются в указателе на функцию-член   типы) никогда не имеют квалификации; см. 8.3.5 dcl.fct.

Короче говоря, вы можете преобразовать из производного D::* в базу B::*, если вы можете преобразовать из B::* в D::*, хотя вы можете использовать только B::* для объектов, которые из типа D или происходят от D.

Ответ 2

Я не уверен на 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;
}

Ответ 3

Критическая проблема с указателями на элементы заключается в том, что они могут применяться к любой ссылке или указателю на класс правильного типа. Это означает, что поскольку 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

Ответ 5

Я так считаю. Поскольку указатель функции использует подпись для идентификации себя, поведение base/производное будет опираться на любой объект, на который вы его называли.

Ответ 6

Мои эксперименты показали следующее: Предупреждение - это может быть поведение undefined. Было бы полезно, если бы кто-то мог дать окончательную ссылку.

  • Это сработало, но потребовалось приведение при назначении производной функции-члена p.
  • Это также сработало, но потребовалось дополнительное отбрасывание при разыменовании p.

Если мы чувствуем себя очень амбициозно, мы можем спросить, может ли p указать на функции-члены несвязанных классов. Я не пробовал, но FastDelegate страница, связанная с ответом на dagorym, предполагает, что это возможно.

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

Литье между функцией-членом указатели - чрезвычайно мрачная область. Во время стандартизации С++, было много дискуссий о должны ли вы указатель функции-члена из одного класса к указателю функции элемента базы или производного класса, и может использоваться между несвязанными классами. К тому времени, когда комитет по стандартам придумали разный компилятор продавцы уже сделали решений, которые заперли их в разные ответы на эти вопросы. [статья FastDelegate]

Ответ 7

Предположим, что 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, который может привести к тому, кто знает, что. Вы можете заставить все это работать, бросая указатели, но это небезопасно или гарантированно работает.

Ответ 8

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

#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;
}