Hi,
I should have said, with this particular example - all of your interface should be in the Shape class (or whatever the base class is) with pure virtual functions. In other words, try to push the interface functions as high up the tree as possible.
Imagine a CAD (Computer Assisted Drawing) program like AutoCAD. There might be a base class called
DrawingEntities
, with classes under that for
Shape
,
Point
,
LineSegment
,
Text
etc. There are basic modification functions like Move, Rotate, Scale and Stretch, and display functions Draw and Hide, all of which apply to all of the drawing entities, but things like area, perimeter and hatch only apply to shapes. So the placement of which virtual functions go in which class would reflect that.
Also consider a
Triangle
class, with derived classes for
Scalene
,
Isosceles
,
Right
,
Equilateral
. The
area(Triangle* ATriangle)
function is the same for all of them
(0.5 * base * height)
, so it exists in Triangle as a virtual function, and not in any of the derived classes at all. As long as there is a
base
and
height
variables to access, this will work with any derived Triangle type passed as an argument. The pure virtual area function in Shape still takes a Shape pointer or reference.
Some might argue that we don't need to derive anything from
Triangle
. That's fine, as along as one doesn't have to resort to poor man's type-ing by having a member variable which says what the type is.
I should also say that references are supposed to work just as well as pointers, but one can't directly have a container of references. There is
std::reference_wrapper
for this purpose, but the use of smart pointers might be better.
The only thing we have to do is add new pure virtual function to Visitor base class every time new class is derived from existing Shape tree. |
No, I disagree. The
visit
function is pure virtual, your code was missing the virtual keyword:
1 2 3 4 5
|
class Visitor{
public:
virtual void visit(Shape&) = 0;
//...
};
|
Because it is pure virtual and takes a Shape reference, it means you explicitly
don't have to provide extra base class functions for each derived class. This goes exactly to the point of what I am saying. You don't have to change the interface, only provide an overridden virtual function in whatever derived class that needs it. And that may not be for
every derived class as explained earlier.
Some other things about polymorphism:
There must be a virtual destructor, otherwise it's Undefined Behaviour (UB)
The "Rule of Zero" often applies, in that you should
default
all five special member functions:
Note the last sentence, you can get out of it sometimes.
There we go some more stuff to think about :+)