2015-08-14 5 views
1

Предположим, я хочу создать иерархию, которая реагирует на определенное событие, закодированное в строке. Например команды, поступающие из сети. Идея заключается в том, что существует класс Base, который обрабатывает сетевое подключение, приемный буфер, разделяет его и т. Д., А обработка реакции команд происходит в производном классе (производный класс также может добавлять некоторые новые слова для обработки). Поэтому мое решение:Метод метода производного класса формы заливки для базового класса

class Base { 
public: 
    typedef void (Base::*Method)(); 
    typedef std::unordered_map<std::string, Method> Methods; 

    void reactToA(); 
    void reactToB(); 

    Base() : 
     methods{ 
      { "A", &Base::reactToA }, 
      { "B", &Base::reactToB } 
     } 
    { 
    } 
    void action(const std::string &s) 
    { 
     auto f = methods.find(s); 
     if(f != methods.end()) 
      (*this.*)(f->second)(); 
    } 

protected: 
    Methods methods; 
}; 

class Child : public Base { 
public: 
    void reactToB(); 
    void reactToC(); 

    Child() { 
     methods[ "B" ] = static_cast<Method>(&Child::reactToB); 
     methods[ "C" ] = static_cast<Method>(&Child::reactToC); 
    } 
}; 

Так что я должен бросить указатель Child метод указатель на Base метода. Является ли это отличное определение? Есть ли более элегантный (или правильный, если это приводит к UB) решение?

+1

Вам не нужно делать актеры, вы всегда будете использовать экземпляр для выполнения обратных вызовов. –

+0

, поскольку написано 'Base' не является базовым классом' Child', это похоже на проблему, с которой надлежащее наследование будет работать даже без приведения. – YoungJohn

+0

Да, вы изобретаете полиморфизм. Любая причина ? – Quentin

ответ

4

Из [expr.static.cast]:

prvalue типа «указатель на член D типа CV1T» может быть преобразовано в prvalue типа «указатель на член B»типа CV2T, где B базовый класс (раздел 10) D, если действительные преобразования стандарта от „указателя на член B типа T“до«точек эр члену D типа T»существует (4.11), и CV2 одно и то же резюме -qualification, как, или больше резюме -qualification, чем, CV1. [...] Если класс B содержит исходный элемент или представляет собой базовый или производный класс класса, содержащего исходный элемент, результирующий указатель на члены указывает на оригинального участника. В противном случае поведение не определено.

В нашем случае &Base::reactToB могут быть преобразованы в &Child::reactToB, но так как Base делает не содержит оригинальный элемент, поведение не определено.

Вам нужно будет хранить что-то вроде std::function<void(Base*)> или void(*)(Base*).

Если первое, то вы можете добавить функцию-член в Base как:

template <typename C> 
void addMethod(std::string const& name, void (C::*method)()) { 
    methods[name] = [method](Base* b){ 
     (static_cast<C*>(b)->*method)(); 
    }; 
} 

addMethod("B", &Child::reactToB); 

Если последнее, вы могли бы сделать что-то вроде:

methods[ "B" ] = +[](Base* b){ 
    static_cast<Child*>(b)->reactToB(); 
}; 
+0

Ваше решение с 'addMethod()' отлично, спасибо – Slava

+0

@KubaOber иногда (особенно при работе с сетью) даже небольшие накладные расходы 'std :: function' могут быть заметны. Также я могу ограничить вызываемые методы как производные классы, а не любые вызываемые. – Slava

+0

Ребята. В 'addMethod()' solution * используется * 'std :: function'. Только версия указателя функции с '+' не работает. – Barry

2

С помощью всего лишь чуть-чуть накладных указатель общей функции std::function, вы можете иметь полностью определенное поведение и гораздо большую гибкость, поскольку вы можете назвать практически все, а не только методы:

class Base { 
public: 
    typedef std::function<void()> Callable; 
    typedef std::unordered_map<std::string, Callable> Callables; 
    void action(const std::string &s) { 
    auto f = callables.find(s); 
    if (f != callables.end()) f->second(); 
    } 
protected: 
    Callables callables; 
}; 

class Derived1 : public Base { 
    void reactToA() {} 
    void reactToB() {} 
public: 
    Derived1() { 
    callables["A"] = std::bind(&Derived1::reactToA, *this); 
    callables["B"] = std::bind(&Derived1::reactToB, *this); 
    } 
}; 

static void reactToE();  

class Derived2 : public Derived { 
    void reactToB() {} 
    void reactToC() {} 
public: 
    Derived2() { 
    callables["B"] = std::bind(&Derived2::reactToB, *this); 
    callables["C"] = std::bind(&Derived2::reactToC, *this); 
    callables["D"] = []{ std::cout << "Hey, what, D now?!" << std::endl; } 
    callables["E"] = &reactToE; 
    } 
}; 
Смежные вопросы