Безопасно ли выгружать указатель на функцию?

У меня есть базовые классы Object и Event

class Object
{
//...
};

class Event
{
};

И typedef для указателя функции

typedef void (Object::*PF) (Event*);

И несвязанный класс, который хранит два указателя

class Dispatcher
{
public:
Dispatcher (Object* obj, PF pf) : _obj (obj), _pf (pf)

void call (Event* event)
{
_obj->*pf (event);
}

Object* _obj;
PF _pf;
};

Тогда у меня есть конкретный объект и конкретное событие

class ConcreteEvent : public Event
{
};

class ConcreteObject : public Object
{
public:
void handle (ConcreteEvent* event)
{
// Do something specific for ConcreteEvent
}
};

А потом назови это так

ConcreteObject* obj = new ConcreteObject();
Dispatcher dispatcher (obj, static_cast<PF>(&ConcreteObject::handle));

ConcreteEvent* event = new ConcreteEvent ();
dispatcher.call (event);

Я гарантирую, что диспетчер всегда будет вызываться с правильным событием, то есть я не буду вызывать диспетчера и передавать его ConcreteEvent когда указатель на функцию он инкапсулирует на самом деле занимает SomeOtherConcreteEvent

Вопрос в том, гарантированно ли это работает? Конечно, прекрасно работает в GCC 4.7 на Linux и MINGW.

3

Решение

Из стандарта C ++ 11, раздел 4.11.2:

Значение типа «указатель на член B типа cv T», где B — это тип класса, может быть преобразовано в
prvalue типа «указатель на член D типа cv T», где D — производный класс (раздел 10) класса B. Если B
недоступный (пункт 11), неоднозначный (10.2) или виртуальный (10.1) базовый класс D, или базовый класс виртуального
Базовый класс D, программа, которая требует этого преобразования, плохо сформирована. Результат преобразования относится
на тот же элемент, что и указатель на элемент до преобразования, но относится к основанию
член класса, как если бы он был членом производного класса.

Так что да, это должно быть безопасно.

Изменить: так что если вы действительно имели в виду понижающее приведение: это также законно, в соответствии с C ++ 11 5.2.9.12:

Значение типа «указатель на член D типа cv1 T» может быть преобразовано в значение типа «указатель на
член B ”типа cv2 T, где B — базовый класс (пункт 10) D, если допустимое стандартное преобразование из
«Указатель на член B типа T» на «указатель на член D типа T» существует (4.11), и cv2 является тем же
cv-квалификация как или более высокая cv-квалификация, чем cv1.
69

4

Другие решения

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

Во-первых, указатель на функции-члены может передаваться только безопасно вниз (потому что ты Derived класс обязательно унаследовал функцию от базового класса, в то время как обратное неверно).

class Base {
public:
void foo();
}; // class Base

class Derived: public Base {
public:
void bar();
};

using PBase = void (Base::*)();
using PDerived = void (Derived::*)();

int main() {
PBase pb = &Base::foo;
PDerived pd = &Derived::bar;

pb = pd; // error: cannot convert 'PDerived {aka void (Derived::*)()}'
//                    to 'PBase {aka void (Base::*)()}' in assignment
pd = pb;
}

(как видно Вот)

Во-вторых, вы не можете изменить тип аргумента Просто так. Чтобы проиллюстрировать проблему, используйте ConcreteObject: public virtual Object и вы увидите, что это не работает так, как вы надеялись.


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

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

using Function = std::function<void(Object*,Event*)>;

void handle(Object* o, Event* e) {
ConcreteObject* co = dynamic_cast<ConcreteObject*>(o);
ConcreteEvent* ce = dynamic_cast<ConcreteEvent*>(e);

if (co and ce) { co->handle(ce); }
}

Или все, что вам нравится / проверяет.

Примечание: использование std::function для совместимости с лямбдами / функторами.

1

По вопросам рекламы [email protected]