Well your code is all broken.
But we will get to that as we answer your questions:
Answers to Questions
How do you keep track of includes? Even in this short example I now have includes that are not needed anymore.
- Only include a header file if you need the class definition.
- Forward declare everything else
Using this rule it is easy to only include files you need. In your header files only include other header files if you need them. You only need them if the object you are defining has a member of that type or is derived from that type (or takes a parameter to a method by value).
In all other situations forward declare.
This reduces the header count considerably.
In the source file you include all the header files of object you use to implement your code (that your header files have not included).
#ifndef BASE_H
#define BASE_H
#include <string> // There is no need for this.
// There are no members that use string
// You are not deriving from string (who would)
// You are not passing a string as a parameter
// Remove this.
#include "IVisitable.h"
class Base : public IVisitable {
public:
void acceptvisitor(Visitor v); // By the way this is probably wrong.
// You are passing by value and thus will
// Make a copy.
// Here you (if you really
// wanted to pass by value) you should have
// include "Visitor.h".
//
// But you don't want to pass by value you want
// to pass by reference. So you just need to
// forward declare.
};
#endif
Is this #ifndef treatment of header files still normal? I picked it up somewhere but have never actually seen anybody else do it.
Yes you should always place include guards around header files to protect from multiple inclusion.
Is there any way to treat an object as a superclass without a pointer? I.e. can I do something like this: Base d = Derived();
Well actually what you have written will compile:
Unfortunately it does not do what you expect. Here you have object slicing. The Base part of the object you created is sliced out and copied into d. What you need is a pointer (or a reference).
It looks like you are used to languages like Java. Where all objects are dynamically allocated. C++ has a much superior mechanism that allows us to accurately control the lifetime of the object. The disadvantage is that it adds complexity to the language.
What is called a pointer in C/C++ (to distinguish it from C++ references) would in most other languages be called a reference. C++ has both local objects (automatic storage duration object) and dynamically allocated objects (dynamic storage duration objects) thus we need a convention that allows access to both types of object, hence we use *
to refer to objects that are dynamically allocated (to distinguish them from local objects).
BUT it is unusual to use pointers directly in C++ (unless you are implementing some real low level stuff). Most of the time when you dynamically allocate objects you will use a smart pointer that defines the lifespan of the object (much like other languages with garbage collection (but better)). std::shared_ptr<T>
would be the equivalent of T
in Java.
std::shared_ptr<Base> d = new Derived(); // dynamically allocated object
// That will be correctly destroyed
// when there are no more references.
When can I use a forward declaration like "class Visitor;" and when do I need to include the actual header file. Or when should I?
As described above.
In the header file (were there is only declaration) only include another header if the header file defines a type that is a member or is used as a parent or used to pass a parameter by value (parameters are infrequent as they are normally passed by reference). In all other cases in a header file you should use forward declaration. In the source file include the header files that define types that you use (ie call methods on).
Note: You only need a forward declaration for objects that are pointers or references.
Should I make implementations of abstract functions virtual? It seems not to matter.
Only virtual methods can be abstract.
If you forget to define a method the compiler will not complain (as you may define it in another compilation unit). The linker will only complain if somebody tries to call the method and can't find a definition. So if you don't call it then there will be no error (but if you don't call it then it does not matter).
When you implement a virtual function in a derived class it is probably best to mark it as virtual to show a subsequent maintainer that it is virtual function (but it is not required by the language).
You should also note that C++11 introduces they keyword override
. Which is an indicator that this method overrides a virtual method in a base class. If there is not such method in the base class it is a compilation error.
Other Notes
Your code passes all parameters by value. This is probably not what you want (as a copy will be made). If it is a derived type the base parameter type will be sliced out and passed to the method. So pass by reference.
class IVisitable {
virtual void acceptvisitor(Visitor& v) = 0;
}; // ^^^ pass by reference
Also note that you can have more than one class defined in a file. Thus personally I would have put the IVisitable
and Visitor
patterns in the same file (they are tightly coupled anyway).