Вопрос

Можно ли в C++ использовать указатели на функции-члены для указания на члены производного (или даже базового) класса?

РЕДАКТИРОВАТЬ:Возможно, пример поможет.Предположим, у нас есть иерархия из трех классов. X, Y, Z в порядке наследования.Y поэтому имеет базовый класс X и производный класс Z.

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

void (Y::*p)();

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

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

Тогда этот вопрос (на самом деле два вопроса):

  1. Может p использоваться для указания на функцию в производном классе Z?
  2. Может p использоваться для указания на функцию в базовом классе X?
Это было полезно?

Решение

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

Значение типа «указатель на член B типа резюме T», где B — тип класса, можно преобразовать в значение r типа «указатель на член D типа резюме T», где D — производный класс (пункт 10) от B.Если B является недоступным (пункт 11), неоднозначным (10.2) или виртуальным (10.1) базовым классом D, программа, требующая этого преобразования, является неправильной.Результат преобразования относится к тому же члену, что и указатель на член до того, как произошло преобразование, но он ссылается на член базового класса, как если бы он был членом производного класса.Результат относится к члену в экземпляре B, принадлежащем D.Поскольку результат имеет тип «указатель на член D типа резюме 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'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:

Значение типа «указатель на член D типа cv1 T» можно преобразовать в значение rvalue типа «указатель на член B типа cv2 T", где B — базовый класс (пункт 10 класс.производное) из D, если существует допустимое стандартное преобразование из «указателя на член B типа T» в «указатель на член D типа T» (4.11 конв.память), и cv2 является той же резюме-квалификацией, что и резюме, или выше, чем, cv1.11) Значение указателя нулевого элемента (4.11 конв.память) преобразуется в значение нулевого указателя элемента целевого типа.Если класс B содержит исходный член или является базовым или производным классом класса, содержащего исходный член, результирующий указатель на член указывает на исходный член.В противном случае результат приведения не определен.[Примечание:хотя класс B не обязательно должен содержать исходный член, динамический тип объекта, на котором разыменовывается указатель на член, должен содержать исходный член;видеть 5.5 выраж.mptr.oper.]

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

Короче говоря, вы можете конвертировать из производного D::* на базу B::* если вы можете конвертировать из 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 может использоваться для указания на функции-члены несвязанных классов.Я не пробовал, но Фастделегат Страница, связанная с ответом Дагорима, предполагает, что это возможно.

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

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