Reputation: 49
My program has an abstract class, BaseNode, with 2 derived classes, ChoiceNode and OpponentNode. I want to write a pure virtual function in the BaseNode called "returnOpposite" which should return an OpponentNode if called from a ChoiceNode, and a ChoiceNode if called from an OpponentNode.
In BaseNode.h
class BaseNode {
protected:
virtual BaseNode& returnOpposite() = 0;
}
In ChoiceNode.h
#include "BaseNode.h"
class ChoiceNode: public BaseNode {
OpponentNode& returnOpposite();
}
In OpponentNode.h
#include "BaseNode.h"
class OpponentNode: public BaseNode {
ChoiceNode& returnOpposite();
}
My problem here is that Opponent/ChoiceNode needs to have knowledge of the opposite class, which would normally be solved with the use of a forward declaration, BUT in order for the compiler to recognize that the opposite class is covariant with BaseNode, it needs contextual information about the class.
It is my understanding that this information is provided by including the appropriate header file. However, doing so results in something of a circular dependency. ChoiceNode would need to include OpponentNode, which itself needs to include ChoiceNode, but with header guards in place, it seems that OpponentNode will not know of the ChoiceNode class declaration.
How do I solve this apparent catch-22? Is there a way to provide the contextual information about a class without getting involved in a circular dependency?
Upvotes: 3
Views: 283
Reputation: 21058
Due to the circular dependency issue you have identified, you cannot use a true co-variant return type, as you've discovered.
However, you can use a technique that worked prior to the introduction of covariant return types.
First, I would recommend changing the declaration in all classes to virtual Node& returnOppositeImpl()
, making the base class pure virtual. I would also, depending on the remainder of your class hierarchy, declare the function and all implementations as private
. You would then declare a non-virtual protected (or public) method in each class returnOpposite
, and using the signatures you described above. These function definitions, because they shadow each other, do not require co-variant return types, and their implementations can be placed in their respective .cpp files. The implementation is a simple static or dynamic cast, invoking the returnOppositeImpl
method.
Using shadowing, you'll always invoke the returnOpposite that corresponds to the static type at the invocation site, so it will have the correct reference type. But the actual knowledge of the linkage between the various Node classes is hidden the the implementation, breaking the dependency loop.
So, taken all together
In BaseNode.h
class BaseNode {
private:
virtual Node& returnOppositeImpl() = 0;
protected: // Or public:
Node& returnOpposite() {
return returnOppositeImpl();
}
}
In ChoiceNode.h
#include "BaseNode.h"
class OpponentNode;
class ChoiceNode: public BaseNode {
private:
virtual Node& returnOppositeImpl();
protected: // or public:
OpponentNode& returnOpposite();
}
In ChoiseNode.cpp
#include "ChoiseNode.h"
#include "OpponentNode.h"
OpponentNode& ChoiseNode::returnOpposite()
{
// You can use static cast here, if Node doesn't have virtual
// members, and/or if you can guarantee further subclasses
// will properly always return an OpponentNode reference.
return dynamic_cast<OpponenentNode&>(returnOppositeImpl());
}
OpponentNode.h and OpponentNode.cpp are similar to the ChoiseNode implementations
Upvotes: 1
Reputation: 27460
All two overridables of returnOpposite()
method shall have the same return type.
Otherwise your code will not compile at all, this abstract method virtual Node& returnOpposite() = 0;
will have no implementations in derived classes.
So your classes should look like this:
class BaseNode {
protected:
virtual BaseNode* returnOpposite() = 0;
}
class ChoiceNode: public BaseNode {
virtual BaseNode* returnOpposite() override;
}
class OpponentNode: public BaseNode {
virtual BaseNode* returnOpposite() override;
}
Update (thanks @DaveS comment): if both two types are covariant (as in your sketch) then the following will work just fine:
class BaseNode {
protected:
virtual BaseNode* returnOpposite() = 0;
}
class OpponentNode; // forward declaration
class ChoiceNode: public BaseNode {
virtual OpponentNode* returnOpposite() override;
}
class OpponentNode: public BaseNode {
virtual ChoiceNode* returnOpposite() override;
}
Note that I am returning here pointers to instances rather than references.
Upvotes: 1