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)

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?

3 Answers
3

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).

+1 for the lambda comment. If you pass visitors by const& you could still modify state if you also hold the state by (smart) pointer and make every visitor member function const.
–
TemplateRexJan 21 '13 at 14:54

@rhalbersma: that's true, but it would be a real pain, don't you think? Also, declaring the visitor member functions as const would be at least as misleading as your objection to alternative 1.
–
riciJan 21 '13 at 15:00

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.

OK, so this would pass around reference_wrapper<Visitor> around in the recursive function?
–
TemplateRexJan 21 '13 at 15:03

Yup, it would basically just prevent copies.
–
ltjaxJan 21 '13 at 15:07

+1 for the Sutter & Alexandrescu quote. The shared_ptr is not an option for performance and corrrectness reasons. There is no issue of ownership here as the data itself will be allocated outside the top-level call of the recursive algorithm, so no visitor is going to outlive the data it points to. So raw pointers should be correct and more efficient here.
–
TemplateRexJan 21 '13 at 15:14

it's not that in (1) there is no state, it's just that the state is shared by many instances of visitor (e.g. in recursive search over a graph keeping track of paths or node counts e.g.)
–
TemplateRexJan 21 '13 at 14:55