I have an abstract Node
class, derived in many subclasses, such as Color
, Texture
, Shape
, Light
, etc... containing my application user data. The data consists in a large tree of these nodes.
Each Node
subclass has a fixed number of children, which are of fixed types. For instance, a Material could have one Color child, and one Texture child. These are stored as std::shared_ptr
's
Currently, each class derives a setChild
method, which takes a Node
as argument. Each implementation checks the type versus its children type with dynamic_cast
accordingly, and sets it if successful.
I would like to implement a generic setChild
method in Node
, without the need for subclassing it. For this, each subclass would declare (or register) its children in the constructor, by giving a name string, a type string (corresponding to the subclass) and a pointer to a shared_ptr
to a Node
.
You can see the problem now:
**SubClass
, upcast to an **Node
, which I know is bad, but since each subclass has a type()
method to uniquely identify the class, and since for each registered child I know its type, I can double check to avoid storing wrong pointer types with the double pointer.**Node
, but with a *std::shared_ptr<Node>
. Here I'm not sure of doing something right.Questions:
shared_ptr<Subclass>
with a *shared_ptr<Node>
even if I'm sure of the type?Thanks,
Etienne
If I understand you, the following is not safe:
boost::shared_ptr<SpecificNode> realPtr;
boost::shared_ptr<Node>* castPtr;
// castPtr = &realPtr; -- invalid, cannot cast from one to the other
castPtr = reinterpret_cast<boost::shared_ptr<Node>*>(&castPtr);
castPtr->reset(anything); // illegal. castPtr does not point
// to a boost::shared_ptr<Node*>
Now you may get lucky and the memory may line up, but that is not valid C++.
If you are looking to extend the set of nodes in 3rd party plugins, the only solution is a series of dynamic casts, so lets see what we can do to make registration work with that.
Instead of trying to do everything with casts, consider using templates to do typesafe actions. Have an abtract base class which accepts a shared ptr to node, and either consumes it, or doens't consume it
(from here on out, I'm going to use T::Ptr instead of boost::shared_ptr, assuming that typedef is there. This is just for stackoverflow ease of reading)
class Registration
{
public:
typedef boost::shared_ptr<Registration> Ptr;
virtual bool consume(const Node::Ptr&) = 0;
};
template <typename T>
class SpecificRegistration : public Registration
{
public:
SpecificRegistration(T::Ptr& inChildPtr)
: mChildPtr(inChildPtr)
{ }
virtual bool consume(const Node:Ptr& inNewValue)
{
if(!inNewValue) {
mChildPtr.reset();
return true; // consumed null ptr
} else {
T::Ptr newValue = dynamic_pointer_cast<T>(inNewValue);
if (newValue) {
mChildPtr = newValue;
return true; // consumed new value
} else {
return false; // no match
}
}
}
private:
T::Ptr& mChildPtr;
};
template <typename T>
Registration::Ptr registerChild(T::Ptr& inChildPtr)
{
return make_shared<SpecificRegistration<T> >(inChildPtr);
}
// you can also register vector<T::Ptr> if you write an
// ArraySpecificRegistration class which uses push_back when
// it consumes a node
void Node::setNode(const Node& inNode) {
for (RegistrationList::iterator iter = mRegistration.begin(); iter != mRegistration.end(); ++iter) {
if (mRegistration->consume(inNode))
return;
}
throw runtime_error("Failed to set any children of the node");
}
class SomeThirdPartyNode
: public Node
{
public:
SomeThirdPartyNode()
{
// all they have to write is one line, and mOtherTHing is
// registered
addChild(registerChild(mOtherThing));
}
private:
SomeOtherThirdPartyNode::Ptr mOtherThing;
};