【问题标题】:Choosing a member function based on a string根据字符串选择成员函数
【发布时间】:2013-11-25 19:11:07
【问题描述】:

我有一组硬件处理程序类,它们都派生自一个基类,它们必须响应传入的数据包。该数据包的一部分是一个 ASCII 字符串,它决定了硬件处理程序的哪个成员函数用于处理数据包(例如“fan”将执行ToggleFan() 函数。

class HardwareHandler {
    virtual void dispatchCommand(const String& cmd) = 0;
}

class FooblerHandler : public HardwareHandler {

    void toogleFan();

    void dispatchCommand(const String& cmd) {
        //is this a "good" way to do this?
        if (cmd == "fan")
            toggleFan();
    }
}

我使用 JUCE 作为框架,这意味着我有模板化的 HashMaps 和 String

但是,我在想出一种基于此字符串选择正确处理函数的简洁方法时遇到了麻烦。构造

if (str == "hello")
    FooCommand();
else if (str == "bar")
    BarCommand();

在概念上对我来说看起来很丑,因为那里有很多相对昂贵的字符串比较。但是,代码很容易编写,并且每个类的逻辑都保存在一个地方。

我尝试过的另一种选择是将字符串的哈希映射到枚举并使用 switch 语句:

switch (str.getHash())
{
case CmdFoo:
    FooCommnad();
    break;
....and so on
}

不过,这也需要我设置一个静态哈希映射,并保持开关匹配。

我尝试的其他方法是从字符串到成员函数指针本身的哈希映射,希望能够直接从字符串跳转到成员函数,而无需在 case 语句中列出它们,并且还允许非常通用调度函数,因为它只需要在哈希映射中查找,它甚至不需要知道所有选项 - 它们可以单独包含在哈希映射中,允许我将调度函数推送到基处理程序类,而不是在每个特定的设备处理程序中重复自己。但是,这种方法让我很困惑,因为我无法完全弄清楚如何正确地做到这一点,或者即使可以使用静态哈希映射和成员函数来做到这一点。

是否有一种惯用的方法来基于字符串(或类似的难以比较的类型)向成员函数分派,最好是尽可能多地使用能够被泛化并移动到父类的逻辑?

【问题讨论】:

  • std::map<std::string, std::function<...> > 怎么样?
  • 所有方法都返回void吗?
  • 不,他们在现实生活中返回bool

标签: c++ hashmap function-pointers dispatch member-function-pointers


【解决方案1】:

这是我的尝试。您可以将映射机制封装到一个类中:

#include <iostream>
#include <string>
#include <functional>
#include <map>

class X;

template<class X>
class handler_factory;

template<>
class handler_factory<X>
{
private:
    using HandlerType = void (X::*)();
public:
    handler_factory();

    HandlerType get(const std::string& name) const
    {
        if (handlers.find(name) == handlers.end())
            return nullptr;
        else
            return (*handlers.find(name)).second;
    }
private:
    std::map<std::string, HandlerType> handlers;
};

class X
{
public:
    friend class handler_factory<X>;
private:
    void f();
    void h();
};

handler_factory<X>::handler_factory()
{
    handlers["f"] = &X::f;
    handlers["h"] = &X::h;
}

void X::f() { std::cout << "X::f();"; }
void X::h() { std::cout << "X::h();"; }

您的调度方法可以实现为:

void dispatch_method(const std::string& name)
{
    if (find_handler(name))
        (this->*find_handler(name))();
}

int main()
{
    X().dispatch_method("f");
}

其中find_handler 被定义为私有辅助方法:

private:
    auto find_handler(const std::string& name)
        -> decltype(handler_factory<X>().get(name))
    {
        return handler_factory<X>().get(name);
    }

【讨论】:

  • handler_factory 是否必须专门用于HardwareHandler 的每个子类(例如FooblerHandlerBarrifierHandler 等)?
  • @Inductiveload 是的,但这只是为了方便。不同的类可能有不同的成员函数签名。如果他们不这样做,handler_factory 不需要模板化,但可以只是一个普通类。
  • @Inductiveload Scratch that:实际上,你没有。如果Bar 是一个继承自Foo 的类,而X 是一个专门用于Foo 的类,那么X&lt;Bar&gt; 就可以正常工作。
【解决方案2】:

我认为处理该问题的最有效方法是创建一个std::map,它将您的字符串映射到适当的函数中。该方法速度快(由于采用对数搜索算法),简单安全。

class FooblerHandler : public HardwareHandler {
   typedef void (HardwareHandler::*function)();
   map<string,function> commandMap;

   void dispatchCommand(const string& cmd) {
      if(commandMap.count(cmd))
         (this->*commandMap.find(cmd)->second)();
      else
         cout << "No command found with name \"" <<cmd<< "\"." << endl;
   }
};

当然你应该在构造函数中初始化地图(或者在使用它之前的某个地方):

commandMap["fan"] = &FooblerHandler::toogleFan;
commandMap["someOtherCommand"] = &FooblerHandler::otherFunction;

Maps 并包含在几乎所有 IDE 提供的标准模板库 (STL) 中。

编辑: 最后没看清楚正文。好吧 - 现在你知道语法了:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-23
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    • 1970-01-01
    • 1970-01-01
    • 2017-09-11
    相关资源
    最近更新 更多