让我们说我想创建一个层次结构来响应以字符串编码的某个事件。例如来自网络的命令。想法是有一个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)解决方案?
答案 0 :(得分:4)
来自[expr.static.cast]:
“指向 cv1
D
类型的T
成员的类型的prvalue”可以转换为类型为“指针”的prvalue cv2B
类型的T
“成员,其中B
是D
的基类(第10条),如果是有效的标准转换从 “指向B
类型T
成员的指针”指向“指向D
T
类型成员的指针”(4.11), cv2 是一样的 cv - 限定为 cv - 资格, 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();
};
答案 1 :(得分: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;
}
};