On Saturday, 4 May 2013 15:36:36 UTC+3, DeMarcus wrote:
> Hi,
>
> The following is a quite common scenario.
>
> int main()
> {
>
> std::vector<Shape*> shapeVector;
> // ...
>
> for( auto& shape : shapeVector )
> {
> streamShape( shape, std::cout );
> }
>
> return 0;
> }
>
> In order to make that happen in a decoupled way we must add quite a
> lot of boiler plate code, usually by means of the Visitor pattern.
Visitors are usually used for a group of objects (usually a tree) that
do not belong to single hierarchy of inheritance; the shapes above
seem to be in a vector and have all common base class. IOW Visitor
seems to be overkill here. Why not to use some virtual function of
'Shape'?
> There are at least two major problems with the Visitor pattern
> though:
>
> * Every class that needs to be streamed must have an accept()
> function which is intrusive but even more disturbing boiler plate
> code.
That is not a big problem. That 'accept' is short and trivial 3-liner.
> * It's not trivial to provide arguments to that accept() function
> since it has to be general for any kind of Visitor where all
> visitors use their own arguments. The StreamVisitor above needs the
> stream as a parameter while a DrawVisitor might need the memory to
> draw to.
Visitors that I have seen have state. It is not business of objects
being visited to affect to what stream that visitor streams them.
The real problem wit Visitors is that when the classes visited need to
be reorganized somehow (merged, split, removed, subclassed or added)
then all the Visitors have to be maintained and retested. That makes
it more expensive to extend and to maintain. Visitor is therefore evil
and so should be used when it is least evil.
> How do you provide Visitor arguments in your code?
I provide them in constructor of visitor. Some printing visitor does
not switch to other printer during its visit to some tree of objects
that it has to print.
Lot of people have difficulties with virtual functions. These
multimethods seem to square the complexity of virtual functions. It
is not a tool for average programmer i'm afraid.
> If we look back at my original problem above it could be solved with
> Stroustrup's technique the following way. Note the /virtual/ keyword
> in the argument list which is their way of saying that the function
> should be resolved in run-time.
>
> void streamShape( virtual Circle* circle, std::ostream& os )
> {
> // ...
> }
>
> void streamShape( virtual Rectangle* rectangle, std::ostream& os )
> {
> // ...
> }
Those are not multimethods. The whole point of a multimethod is to
have multiple virtual parameters.
> With Stroustrup et. al.'s simple technique we solve all the
> uncomfortable problems with the Visitor pattern. The solution that
> they provide solves more than just the Visitor pattern though, hence
> it also implies quite a restructuring of the virtual table.
>
> However, correct me if I'm wrong now, but what we do above is to use
> only one virtual argument, so we only use /Single/ Dispatch. Single
> Dispatch we already have in C++ with the normal virtual methods so
> in order to implement Single Dispatch also for free functions as we
> do above, we could simply use the virtual table we already have.
Yes. Exactly. We should always aim to use simplest solution that
solves the problem (but not simpler).
> I see that Single Dispatch for free functions could be a good start
> to introduce Stroustrup et. al.'s Multimethods technique. What do
> you think?
I think that most understand it better if we just use the already
existing single dispatch with already existing syntax:
void Circle::streamTo( std::ostream&) const
{
// ...
}
void Rectangle::streamTo( std::ostream&) const
{
// ...
}
int main()
{
std::vector<Shape*> shapeVector;
// ...
for( auto& shape : shapeVector )
{
shape.streamTo( std::cout );
}
return 0;
}
If you need a free function (why?) then you can make it to call that
virtual 'streamTo' and done.