Наследование наследования С++ и указатели функций-членов
В С++ можно использовать указатели функций-членов, чтобы указывать на производные (или даже базовые) члены класса?
EDIT:
Возможно, пример поможет. Предположим, что мы имеем иерархию из трех классов X
, Y
, Z
в порядке наследования.
Y
поэтому имеет базовый класс X
и производный класс Z
.
Теперь мы можем определить указатель на функцию-член p
для класса Y
. Это записывается как:
void (Y::*p)();
(Для простоты я предполагаю, что нас интересуют только функции с сигнатурой void f()
)
Этот указатель p
теперь можно использовать для указания на функции-члены класса Y
.
Этот вопрос (на самом деле, два вопроса):
- Можно использовать
p
для указания функции в производном классе Z
?
- Можно использовать
p
для указания функции в базовом классе X
?
Ответы
Ответ 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
Ответ 4
Возможно, вы захотите проверить эту статью Указатели функций-членов и самые быстрые делегаты С++. Короткий ответ кажется да, в некоторых случаев.
Ответ 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;
}