C ++ указатели на функции-члены в классе и подклассе - PullRequest
7 голосов
/ 27 сентября 2011

У меня есть один базовый класс, который содержит map для таких указателей на функции, как это

typedef void (BaseClass::*event_t)();
class BaseClass {
    protected:
        std::map<std::string, event_t> events;
    public:
        // Example event
        void onFoo() {
            // can be added easily to the map
        }
};

Обработка этого префекта работает, но теперь я хочу сделать BaseClass абстрактный базовый класс, производный от которого, вот так:

 class SpecificClass : public BaseClass {
     public:
         void onBar() {
             // this is gonna be difficult!
         }
 };

Хотя я могу получить доступ к карте с SpecificClass, я не могу добавить onBar, потому что тип event_t определен только для BaseClass! Есть ли возможность (возможно, с шаблонами?), Которая не приводит к определению event_t для каждого класса, который я буду использовать ...

(Использовать шаблоны не обязательно! Любой хороший / подходящий подход был бы хорош.)

Дополнительная справочная информация:

Это все для текстовой RPG. Мой базовый класс может называться Location, а указанный может быть в любом месте, например CivicCenter. Каждый объект Location подписывается на мой EventSystem, который уведомляет все необходимые объекты, когда я запускаю событие. Поэтому я хочу сохранить на карте некоторые указатели на частные функции, содержащие действия с их «именем», таким как onSetOnFire (xD) в качестве ключа.

Ответы [ 4 ]

2 голосов
/ 28 сентября 2011

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

class Location {
    // ...

    protected:
        std::map<std::string, std::function<void(void)>> m_mEvents;
};

И теперь я могу справиться с этим так:

class CivicCenter : public Location {
    public:
        CivicCenter() {
            // this is done by a macro which lookes better than this
            this->m_mEvents["onTriggerSomething"] =
                  std::bind(&CivicCenter::onTriggerSomething, this);
        }

        void onTriggerSomething() {
            // ...
        }

    // ...
};

С легким использованием std::bind я могу реализовывать указатели универсальных функций. При использовании таких параметров, как в std::function<void(int, int)>, не забудьте использовать _1 и _2 boost или выражения lambda, подобные мне:

std::function<void(int,int)> f = [=](int a, int b) {
    this->anotherFunctionWithParams(a, b);
};

Но на это указывает только полнота моего решения.

2 голосов
/ 27 сентября 2011

Это не может быть сделано с вашим текущим map как есть. Подумайте, что произойдет, если вы сможете поместить дочерний метод в карту. Затем вы можете вытащить указатель на дочерний элемент (маскирующийся как базовый) из карты, вызвать его для указателя экземпляра базового класса, а затем как он будет вызывать производный класс для экземпляра базового класса, который, очевидно, не может работа.

Будет ли работать полиморфный подход?

2 голосов
/ 27 сентября 2011

Да; прекратить использование указателей членов.

Более правильный способ сделать то, что вы хотите - это иметь тип события и указатель объекта. Таким образом, событие срабатывает на конкретном объекте. Тип события: функция, не являющаяся членом (или статический член). Было бы передано указатель объекта. И это вызвало бы некоторую фактическую функцию-член этого объекта.

В настоящее время тип события может быть std/boost::function. Однако, поскольку параметры функции должны оставаться одинаковыми для всех событий, это не решит вашу проблему. Вы не можете вызвать SpecificClass::onBar из указателя BaseClass, если не выполните приведение к SpecificClass. И функция вызова событий не знает, как это сделать. Таким образом, вы все еще не можете поместить SpecificClass::onBar в объект std/boost::function; вам все еще нужна какая-то отдельная функция, чтобы выполнить приведение за вас.

Все это просто кажется ужасным использованием полиморфизма. Почему SpecificClass нужно вообще выводить из BaseClass? Разве они не могут быть двумя несвязанными классами?

1 голос
/ 27 сентября 2011

Вы должны использовать static_cast:

event_t evt = static_cast<event_t>(&SpecificClass::onBar);

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

Как это работает (для скептиков):

class BaseClass {
public:
    typedef void (BaseClass::*callback_t)(); // callback method
    void doSomething(callback_t callback) {
        // some code
        this->*callback();
        // more code
    }
    void baseCallback(); // an example callback
};

class DerivedClass : public BaseClass {
public:
    void derivedCallback();
    void doWhatever() {
        // some code
        doSomething(&BaseClass::baseCallback);
        // more code
        doSomething(static_cast<callback_t>(&DerivedClass::derivedCallback));
        // et cetera
};

Вот что вы должны избегать, и почему это потенциально опасно:

void badCodeThatYouShouldNeverWrite()
{
    BaseClass x;
    // DO NOT DO THIS IT IS BAD
    x.doSomething(static_cast<callback_t>(&DerivedClass::derivedCallback));
}

Требование для static_cast делает его таким, чтобы вы не могли "случайно" передать указатели на метод DerivedClass. И если вы считаете, что это опасно, просто помните, что это указатель, а указатели всегда опасны. Конечно, есть способы сделать это, которые включают создание вспомогательных классов, но это требует много дополнительного кода (возможно, создание класса для каждой функции, которую вы хотите передать в качестве обратного вызова). Или вы можете использовать замыкания в C ++ 11 или что-то из Boost, но я понимаю, что многие из нас не имеют такой возможности.

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