【问题标题】:Visitor class holding large shared state: best way to implement reference semantics?持有大共享状态的访问者类:实现引用语义的最佳方式?
【发布时间】:2013-01-04 14:49:23
【问题描述】:

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

由于visitor参数是按值传递的,如果你的visitor包含状态,那么算法过程中对状态的任何改变都将复制到visitor对象,而不是传入的visitor对象。因此你可能想要visitor通过指针或引用保持此状态。

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

替代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();
}

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

【问题讨论】:

  • @drive-by downvoter:动机?

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


【解决方案1】:

还有第三种选择:

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::bind 传递std::vector 时。

备选方案 1,带有shared_ptr&lt;T&gt;(或更好的是:shared_ptr&lt;T const&gt;)可能是您的最佳选择。在任何一种情况下,您都只是将指针语义“包装”在 BGL 代码的值语义后面,只要您正确获取所有对象生命周期就可以了。

【讨论】:

  • 好的,所以这会在递归函数中绕过reference_wrapper&lt;Visitor&gt;
  • 是的,它基本上只是防止复制。
  • Sutter & Alexandrescu 报价+1。出于性能和正确性的原因,shared_ptr 不是一个选项。这里没有所有权问题,因为数据本身将在递归算法的顶级调用之外分配,因此没有访问者会比它指向的数据更长寿。所以这里的原始指针应该是正确的和更有效的。
【解决方案2】:

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

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

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

【讨论】:

  • +1 表示 lambda 注释。如果您通过const&amp; 传递访问者,如果您还通过(智能)指针保存状态并使每个访问者成员函数const,您仍然可以修改状态。
  • @rhalbersma:确实如此,但这将是一个真正的痛苦,你不觉得吗?此外,将访问者成员函数声明为 const 至少与您反对备选方案 1 一样具有误导性。
【解决方案3】:

当你的访问者实际上有一个状态时,试图让它成为无状态是没有意义的。我认为 (2) 没有任何问题。

【讨论】:

  • 这并不是说在 (1) 中没有状态,只是状态由许多访问者实例共享(例如,在跟踪路径或节点计数的图上进行递归搜索)
猜你喜欢
  • 2011-09-16
  • 1970-01-01
  • 1970-01-01
  • 2010-09-07
  • 2011-03-11
  • 2016-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多