持有大型共享状态的访问者类:实现引用语义的最佳方法?

时间:2013-01-21 14:14:39

标签: c++ pass-by-reference pass-by-value boost-graph visitor-pattern

这个问题基于Boost.Graph库(BGL),它使用类似Visitor的模式来定制递归(搜索)算法。 BGL按值传递访问者对象(类似于STL函数对象)和documentation状态

  

由于visitor参数是按值传递的,如果访问者包含状态,那么算法期间对状态的任何更改都将对访问者对象的副本进行,而不是传入的访问者对象。因此,您可能希望访问者通过指针或引用保持此状态。

我的问题:实现有状态访问者类的引用语义的最佳方法是什么?从精确的指针类(原始vs唯一vs共享,const与非const)中抽象出来,放置引用的最佳位置是什么:在参数传递中还是在数据成员中?

备选方案1 :访问者通过指针保存状态,并按值传递(如Boost.Graph中所示)

class Visitor
{
public:
    Visitor(): state_(new State()) {}
    void start() { /* bla */ }
    void finish() { /* mwa */ }
private:
    State* state_;
}

template<typename Node, typename Visitor>
int algorithm(Node const& n, Visitor v)
{
    v.start();
    algorithm(next(n), v);
    v.finish();
}

备选方案2 :访问者按值保存数据,并通过指针传递

class Visitor
{
public:
    Visitor(): state_() {}
    void start() { /* bla */ } 
    void finish() { /* mwa */ }
private:
    State state_;
}

template<typename Node, typename Visitor>
int algorithm(Node const& n, Visitor* v)
{
    v->start();
    algorithm(next(n), v);
    v->finish();
}

我目前的倾向:我发现Alternative 1 [传递持有指针/引用的对象的值]有点不舒服,因为访问者不满足值语义,所以我宁愿参考语义清除参数列表[备选方案2]。这里有其他相关的考虑因素或替代方案吗?

3 个答案:

答案 0 :(得分:2)

还有第三种选择:

class Visitor
{
public:
    Visitor(): state_() {}
    void start() { /* bla */ } 
    void finish() { /* mwa */ }
private:
    State state_;
};

template<typename Node, typename Visitor>
int algorithm(Node const& n, Visitor v)
{
    v.start();
    algorithm(next(n), v);
    v.finish();
}

// Set the reference semantics here, use value everywhere else
algorithm(myNode, boost::ref(myVisitor)); // ... or std::ref in c++11

我认为这通常受到标准的青睐,而不是明确地将某些东西标记为指针或引用。毕竟,已引入std::refstd::cref来解决此问题。

另一方面,在“C ++编码标准”一书中,Sutter和Alexandrescu认为仿函数应该总是容易且快速地复制。他们建议在内部使用引用计数状态块(IIRC,这里没有这本书)。因此,虽然std::refstd::cref可以解决您的问题,但它们更常用于“适应”非仿函数对象,例如通过std::vectorstd::bind时。

备选方案1,shared_ptr<T>(或更好:shared_ptr<T const>)可能是您的最佳选择。在任何一种情况下,您只是将指针语义“包装”在BGL代码的值语义之后,只要您正确地使用所有对象生命周期,这是可以的。

答案 1 :(得分:1)

我理解您对备选方案1的不适,但我认为这是“公共汽车已经离开”的情况;换句话说,C ++标准库(以及Boost,而不仅仅是BGL)的方向倾向于使用“保持引用”模式。

例如,考虑一下可以用lambda表达式实现的仿函数的普遍使用。据我所知,所有标准库(和boost)接口都按值传递仿函数参数,因此如果仿函数保持状态,它必须通过引用保存它。因此,我们应该习惯于看[&](){}而不是[=](){}。而且,通过类比,我们应该习惯看到访客持有他们的州的引用(或指针,但我更喜欢引用)。

实际上有理由通过值而不是引用来传递函子(和访问者)。如果它们是通过引用传递的,那么它们必须通过const&传递,这将使状态修改成为不可能,或者通过&,这将使临时值无法使用。唯一的另一种可能性是传递显式指针,但不能与lambdas或临时值一起使用(除非临时值不必要地堆分配)。

答案 2 :(得分:0)

在实际拥有状态时,尝试让访问者无状态是没有意义的。我认为(2)没有任何问题。