Visitor class holding large shared state: best way to implement reference semantics?

依然范特西╮ 提交于 2019-12-10 16:15:31

问题


This question is loosely based on the Boost.Graph library (BGL) that uses a Visitor-like pattern to customize recursive (search) algorithms. The BGL passes visitor objects by value (in analogy with the STL function objects) and the documentation states

Since the visitor parameter is passed by value, if your visitor contains state then any changes to the state during the algorithm will be made to a copy of the visitor object, not the visitor object passed in. Therefore you may want the visitor to hold this state by pointer or reference.

My question: what is the best way to implement the reference semantics of stateful visitor classes? Abstracting from the precise pointer classes (raw vs unique vs shared, const vs non-const), what would be the best place to put the reference: in the parameter passing or in the data member?

Alternative 1: visitor holds state by pointer, and is passed by-value (as in 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();
}

Alternative 2: visitor holds data by-value, and is passed by-pointer

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

My current inclination: I find Alternative 1 [passing by-value of objects that hold pointers/references] a little uncomfortable because the visitor does not satisfy value semantics, so I would rather make the reference semantics clear in the parameter list [Alternative 2]. Are there any other considerations or alternatives that are relevant here?


回答1:


I understand your discomfort with Alternative 1, but I think that this is a case of "that bus has left"; in other words, the direction of the C++ standard library (and of Boost, not just BGL) favours the use of the "held reference" pattern.

Consider, for example, the pervasive use of functors which can be implemented with lambda expressions. As far as I know, all standard library (and boost) interfaces pass functor arguments by value, so if the functor holds state, it must hold it by reference. Consequently, we should get used to seeing [&](){} rather than [=](){}. And, by analogy, we should get used to seeing Visitors hold references (or pointers, but I prefer references) to their state.

There is actually a good reason to pass functors (and visitors) by value rather than by reference. If they were passed by reference, they would have to be passed either by const&, which would make state modification impossible, or by &, which would make using temporary values impossible. The only other possibility would be passing an explicit pointer, but that couldn't be used with lambdas or temporary values (unless the temporary values were unnecessarily heap-allocated).




回答2:


There's a third alternative:

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

I think this is usually favored by the standard as opposed to explicitly marking something as a pointer or a reference. After all, std::ref and std::cref have been introduced to solve this problem.

On the other hand, in the book "C++ Coding Standards", Sutter and Alexandrescu argue that functors should always be easy and fast to copy. They recommend using a reference counted state block internally (IIRC, don't have the book here). So while std::ref or std::cref would solve your problem, they are more commonly used to "adapt" non-functor objects, e.g. when passing a std::vector through std::bind.

Alternative 1, with a shared_ptr<T> (or better yet: shared_ptr<T const>) is probably your best option. In either case, you are just "wrapping" your pointer semantics behind value semantics for the BGL code, which is okay as long as you get all the object lifetimes right.




回答3:


It's pointless to try to make your Visitor stateless when actually it has a state. I don't see any problem with (2).



来源:https://stackoverflow.com/questions/14440501/visitor-class-holding-large-shared-state-best-way-to-implement-reference-semant

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!