在运行时更改谓词

时间:2013-01-26 03:03:17

标签: c++ stl

假设您使用不同的谓词(在此特定情况下具有初始状态的函数对象),您使用STL算法(copy_ifsort等...)。事情是谓词可以在运行时通过配置更改或用户输入进行更改。我曾经想过使用多态和虚拟operator(),然后确定了这样的std::function解决方案(这使我进入了C ++ 11领域,但没关系)

struct PlainFilter {
    PlainFilter(string filter):m_filter(filter)
    {}
    bool operator() (const string& toMatch)
    {one way}
};
struct AcronymFilter {
    AcronymFilter (string filter):m_filter(filter)
    {}
    bool operator() (const string& toMatch)
    {a different way}
};

enum FilterTypes {plain,acronym};

vector<string> FilterStuff(string filter, vector<string> in)
{
     vector<string> out;
     std::function<bool(const string&)> foo;

     if( filterType == plain)
         foo = PlainFilter(filter);
     else if( filterType == acronym)
         foo = AcronymFilter(filter);

     copy_if(in.begin(),in.end(),back_inserter(out),foo);
     return out;
} 
这是好事吗?

我宁愿避免每次我需要过滤字符串的if语句,因为过滤器类型可能会在程序的整个生命周期中一次更改或根本不更改。  对这个问题的任何其他不同看法也是受欢迎的。

3 个答案:

答案 0 :(得分:0)

可能有几种方法可以做到这一点,但这就是多态性的用途。您的代码已经过简化,您无需记住为每个可以使用的地方添加新的caseelse if

struct IFilter
{
    virtual bool operator()(const std::string &) const = 0;
};

struct PlainFilter : public IFilter
{
    virtual bool operator()(const std::string &filter) const override
    {
        // do something
    }
};

struct AcronymFilter : public IFilter
{
    virtual bool operator()(const std::string &filter) const override
    {
        // do something else
    }
};


std::vector<std::string> FilterStuff(const IFilter &filter, const std::vector<std::string> &in)
{
    std::vector<std::string> out;
    std::copy_if(in.begin(), in.end(), std::back_inserter(out), filter);

    return out;
}

但是,我个人会以不同方式实施FilterStuff。您从一个string获取vector并将它们复制到另一个{},然后可能是另一段代码将迭代新的vector并对那些已过滤的string执行某些操作1}}秒。相反,请考虑采用过滤器“使用它做某事”功能的设计:

void EnumerateStuff(const IFilter &filter, const std::vector<std::string> &in,
                    std::function<void(std::string)> callback)
{
    for (const auto &s : in)
    {
        if (filter(s))
        {
            callback(s);
        }
    }
}

FilterStuff现在可以用EnumerateStuff来编写,如果真的有必要使用过滤后的副本:

std::vector<std::string> FilterStuff(const IFilter &filter, const std::vector<std::string> &in)
{
    std::vector<std::string> out;

    EnumerateStuff(filter, in,
        [&](const std::string &s)
        {
            out.push_back(s);
        });

    return out;
}

答案 1 :(得分:0)

您示例中的filterType变量是什么?我想你的应用程序/算法的一些可配置参数?

无论如何,我建议如下:

1)将所有可配置参数收集到一个结构中:

class configuration
{
public:
    /// Type of predicate functor
    typedef std::function<bool(const std::string&)> predicate_type;

    struct plain_filter { /* your implementation */ };
    struct acronym_filter { /* your implementation */ };

    /// Type of predicate to use
    enum class predicate_type { plain, acronym };

    /// Set predicate
    void set_filter_kind(const predicate_type ft)
    {
        switch (ft)
        {
        case predicate_type::plain:
            m_predicate = plain_filter();
            break;
        case predicate_type::acronym:
            m_predicate = acronym_filter();
            break;
        default:
            assert(!"Invalid filter type");
        }
    }

    /// Get filter to be used by algorithms
    /// \todo Consider to return a const reference instead of copy,
    /// but make sure your filters are state-less (as expected by STL slgorithms)
    decltype(m_predicate) use_filter() const
    {
        return m_predicate;
    }

    // other configuration parameters/methods

private:
    predicate_type m_predicate;
};

2)从命令行选项,配置文件或用户输入填充configuration的实例。使此实例对代码的所有必需部分可见(例如,使其成为application类的成员,并提供一个方法(只读)访问它或像这样smth ...)

3)在算法中使用配置数据

std::vector<string> filter_stuff(string filter, const std::vector<string>& in)
{
    std::vector<string> out;
    std::copy_if(
        begin(in)
      , end(in)
      , std::back_inserter(out)
      , application().get_config().use_filter()
      );
    return out;
}

PS:顺便说一句,通过引用传递in参数(或右值引用)......我真的怀疑你需要一个副本(按值传递)

答案 2 :(得分:0)

由于filterType是运行时值,因此您将在此处进行某种选择。有你的ifswitch或数组查找。我最喜欢switch,数组查找需要更复杂的类型,一旦优化器完成它就不会更快。另外,我会通过const &传递初始值设定项,而不仅仅是反射值。

那就是说,我认为你的方法很好。另一种方法:

bool plain_string_test(const string& filter, const string& candidate) 
{  /* ... one way ... */ }

bool acronym_string_test(const string& filter, const string& candidate)
{  /* ... or another ... */ }

enum FilterTypes {plain,acronym};

vector<string> FilterStuff(string filter, vector<string> in)
{
     vector<string> out;
     std::function<bool(const string&, const string&)> filtermethod;

     switch(filterType) {
     default: throw some_domain_error(some_constructor_here);

     case plain:      filtermethod = plain_string_test;         break;
     case acronym:    filtermethod = acronym_string_test;       break;

     }

     copy_if(in.begin(),in.end(),back_inserter(out), 
         [&filtermethod, &filter](const string& candidate) -> bool
             { return filtermethod(filter,candidate); }
     return out;
}

我更喜欢这个;它消除了一些类型的脚手架和一些复制,使字符串测试更可重用(或者也消除了一些函数脚手架)。在这里,您可以从静态函数数组中获得更好的价值,它可能取决于上下文。

键盘到编辑框警告,我还没有测试过这段代码,但我相信它至少对于沟通来说是正确的。