почему я не могу указать указатель на функцию-член класса Derived на то же самое, но на класс Base?

Мне кажется совершенно безопасным приведение void(Derived::*)() к void(Base::*)(), как в этом коде:

#include <iostream>
#include <typeinfo>
using namespace std;
struct Base{
    void(Base::*any_method)();
    void call_it(){
        (this->*any_method)();
    }
};
struct Derived: public Base{
    void a_method(){
        cout<<"method!"<<endl;
    }
};
int main(){
    Base& a=*new Derived;
    a.any_method=&Derived::a_method;
    a.call_it();
}

Но компилятор жалуется на приведение в a.any_method=&Derived::a_method;. Является ли это препятствием для предотвращения незаметных ошибок программирования или просто чем-то, что облегчает жизнь разработчикам компиляторов? Существуют ли обходные пути, позволяющие классу Base иметь указатель на функции-члены Derived без знания типа (то есть я не могу сделать Base шаблоном с аргументом шаблона Derived).


person Lorenzo Pistone    schedule 15.04.2012    source источник


Ответы (4)


Что произойдет, если ваш Derived::a_method() попытается использовать элемент данных, присутствующий только в Derived, а не в Base, и вы вызовете его для объекта Base (или объекта, производного от Base, но не связанного с Derived)?

Преобразование наоборот имеет смысл, это - нет.

person Mat    schedule 15.04.2012

Нет, это потенциально опасно.

Функция производного класса может использовать все свойства производного класса *this. Указатель на функцию базового класса может быть вызван для любого экземпляра базового класса, даже для тех, которые не относятся к производному типу.

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

С другой стороны, приведение указателя на функцию базового класса к указателю на функцию производного класса является безопасным и законным.

person CB Bailey    schedule 15.04.2012

Вам нужно использовать std::function<void()>. Это может быть любой член любого класса, лямбда, свободная функция, объект функции, что угодно, что вам нужно, что очень удобно.

#include <iostream>
#include <typeinfo>
using namespace std;
struct Base{
    std::function<void()> any_method;
    void call_it(){
        any_method();
    }
};
struct Derived: public Base{
    void a_method(){
        cout<<"method!"<<endl;
    }
};
int main(){
    Derived* d = new Derived;
    Base& a= *d;
    a.any_method = [d] { d->a_method(); };
    a.call_it();
}

Здесь вы можете видеть, что фактическая реализация any_method полностью абстрагирована от struct Base, и я могу предоставить объект функции, который делает что угодно, включая удобный вызов метода Derived.

person Puppy    schedule 15.04.2012
comment
Не очень понятно, как здесь может помочь std::function: у std::function<void(Base::*)> та же проблема, и если отказаться от присвоения any_method типа функции-члена, то вообще нет смысла использовать std::function вместо простого указателя на свободную функцию. Верно? - person Lorenzo Pistone; 15.04.2012
comment
@Лоренцо: Нет. std::function<void()>. Функция может хранить собственный указатель this. Кроме того, всегда есть точка std::function поверх лямбда-выражений указателя функции с состоянием, например, результат std::bind. Я отредактировал свой ответ, чтобы сделать более очевидным, как std::function мгновенно решает эту проблему. - person Puppy; 15.04.2012

Я думаю, это может быть несколько удивительно. Однако это имеет смысл, если подумать.

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

Например, если d является экземпляром Derived, то его можно автоматически преобразовать в Base&, поскольку любой экземпляр Derived также является экземпляром Base. Это наследование.

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

Другой способ визуализировать это — использовать свободные функции. this — это просто неявный параметр в обычной функции, если мы сделаем его явным, то получим:

void Base@call_it(Base& self);

void Derived@a_method(Derived& self);

Теперь, если у меня есть два экземпляра d типа Derived и b типа Base, тогда:

  • Base@call_it(d) имеет смысл
  • Derived@a_method(b) - ошибка компиляции

Последним может быть Derived@a_method(dynamic_cast<Derived&>(b)), но это вводит проверку во время выполнения для фактической проверки свойства. Статически это неразрешимо.

person Matthieu M.    schedule 15.04.2012