在 C++ 聚合类中实现调用多路复用的优雅方式?

Posted

技术标签:

【中文标题】在 C++ 聚合类中实现调用多路复用的优雅方式?【英文标题】:Elegant way of implementing call multiplexing in a C++ aggregate class? 【发布时间】:2011-08-29 12:12:55 【问题描述】:

当多路复用对许多子对象的调用时,防止循环样板代码的优雅方法是什么?

问题举例说明:

struct Foo 
  void Boo();
  void Hoo();
  bool IsActivated();
;

struct FooAggregator 
  ...
  void Boo();
  void Hoo();
  ...
  std::vector<Foo> m_foos;
;

FooAggregator::Boo() 
  for(size_t i=0, e=m_foos.size(); i!=e; ++i) 
    if(m_foos[i].IsActivated()) 
      m_foos[i].Boo();
    
  


FooAggregator::Hoo() 
  for(size_t i=0, e=m_foos.size(); i!=e; ++i) 
    if(m_foos[i].IsActivated()) 
      m_foos[i].Hoo();
    
  

如您所见,FooAggregator 实现了与单个 Foo 相同(相似)的接口,迭代所有调用其各自成员函数的 Foo 对象。

您也可以看到,迭代循环是完整的样板,针对 FooAggregator 的每个成员函数重复。

什么是从 FooAggregators 成员函数的实现中删除样板的优雅方法

【问题讨论】:

这 ´e=m_foos.size(); i!=e´ 看起来像是过早的优化。 @Henrik:不是这样。这只是惯用的编程。所有循环看起来都一样。 【参考方案1】:

我将采用 Nawaz 的第一个很好的例子并进一步简化:

(记住,我想减少样板,而不是引入最花哨的功能。)

// FooAggregator.h
struct FooAggregator 
    template<typename MemFn>
    void CallForEachFoo(MemFn fun);

    void Boo();
    void Hoo();
;

// FooAggregator.cpp
template<typename MemFn>
void FooAggregator::CallForEachFoo(MemFn fun) 
    BOOST_FOREACH(Foo& o, m_foos) 
      if(o.IsActivated()) 
        (o.*fun)();
      
    


void Boo()   CallForEachFoo(&Foo::Boo); 
void Hoo()   CallForEachFoo(&Foo::Hoo); 

【讨论】:

【参考方案2】:

Nawaz 的回答很有趣,但还有其他解决方案。

首先,您应该认识到您的聚合器在很大程度上是一个Composite 模式。

其次,我会选择:

外部迭代 一个类似for_each 的成员方法,一个函子被传递给它(实际上是2,因为const 重载)。

对于外部迭代,请继续阅读:)

相对不幸的是,C++ 迭代器语法并不真正适合“跳过”迭代器,但它仍然可以实现。

class ActiveIterator 
public:
  friend class FooAggregator;

  friend bool operator==(ActiveIterator lhs, ActiveIterator rhs) 
    return lhs._it == rhs._it;
  

  ActiveIterator& operator++() 
    this->next();
    return *this;
  

  Foo* operator->() const  return _it::operator->(); 
  Foo& operator*() const  return *_it; 

private:
  typedef std::vector<Foo>::iterator base;
  ActivateIterator(base begin, base end): _it(begin), _end(end) 
    if (_it == _end || _it->IsActive())  return; 
    this->next();
  

  void next() 
    ++it; while (_it != _end && !_it->IsActive())  ++_it; 
  

  base _it, _end;
;

然后,您的聚合仅具有 BeginEnd 方法,由调用者与您的迭代器进行交互。

注意:您可以将其模板化为一次性实现可变/常量

但外部迭代仍然非常庞大,因为 C++ 缺少生成器语法来使事情变得简单。

【讨论】:

【参考方案3】:

您可以按照@Space_C0wb0y 的建议使用Boost.Bind。但是如果你因为某种原因不能使用它,那么你可以做这样的事情:

struct FooAggregator 

    typedef void (Foo::*Fun)();


    void Boo()   CallForEach(m_foos.begin(), m_foos.end(), &Foo::Boo); 
    void Hoo()   CallForEach(m_foos.begin(), m_foos.end(), &Foo::Hoo); 

    template<typename FwdIterator>
    void CallForEach(FwdIterator first, FwdIterator last, Fun fun)
    
        while (first != last ) 
         
            if(first->IsActivated())
            
                 (first->*fun)();
            
            first++;
        
    
;

或者你可以使用std::for_each from &lt;algorithm&gt; as:

#include <algorithm>

struct FooAggregator 

    typedef void (Foo::*Fun)();

    void Boo()   std::for_each(m_foos.begin(), m_foos.end(), Call(&Foo::Boo)); 
    void Hoo()   std::for_each(m_foos.begin(), m_foos.end(), Call(&Foo::Hoo)); 

    struct Call
    
        Fun m_fun;
        Call(Fun fun) : m_fun(fun) 
        void operator()(Foo & foo)
        
            if(foo.IsActivated())
            
               (foo.*m_fun)();
            
        
   ;    
;

阅读 Function object 以了解第二个示例。


在 C++0x(即 C++11)中,它非常简单。您可以在std::for_each 中使用 lamda 作为:

#include <algorithm>

struct FooAggregator 

    void Boo()
      
         std::for_each(m_foos.begin(), m_foos.end(), [](Foo &foo) if (foo.IsActivated()) foo.Boo();  ); 
    

    void Hoo()
      
         std::for_each(m_foos.begin(), m_foos.end(), [](Foo &foo) if (foo.IsActivated()) foo.Hoo();  ); 
    
    //other code
;

【讨论】:

这是朝着正确方向迈出的一步,除了:1)有std::foreach(),2)如果函数的签名不同,这将需要一些std/boost::bind() 魔法。或者使用 lambda 函数,如果你的编译器有这些。 @sbi:但是,foreach 需要将条件包装到一个额外的函数中。 @Space_C0wb0y:这就是我提出bind 和 lambda 的原因。 @Nawaz:太好了!赞成。 +1 用于第一个示例。我不喜欢第二个。他们要冗长。 (因为我想减少样板,而不是引入看起来不同的样板:-)【参考方案4】:

您可以使用Boost.Bind 将boost::function 对象传递给指定调用哪个方法的调度方法。那么您只需要一个可以使用不同目标方法作为参数调用的调度方法。

【讨论】:

以上是关于在 C++ 聚合类中实现调用多路复用的优雅方式?的主要内容,如果未能解决你的问题,请参考以下文章

通过在 C++ 中实现类似“map”函数的 python 问题:调用类成员函数

NIO多路复用的终极奥义

Java中实现多线程的两种方式

Linux内核poll内部实现

IO多路复用简介

在 Hibernate 中实现基于条件的搜索页面的优雅方式