问题
I am writing a quadtree class as a part of graphics library and I am facing a design issue. A main goal is to allow the user of the library to easily extend the quadtree with their own Node Types. Each node has a pointer to the first of its four childs. I use the protoype pattern to 'clone' a parent node (its real type is unknown to the library) four times when it is split. So here is the Node class:
class CNode {
public:
virtual CNode* clone();
protected:
CNode* pChilds;
}
The user of the library may now define its own node and add a traverse method:
class MyNode : public CNode {
public:
virtual CNode* clone() {
return new MyNode;
}
void myTraverse() {
if(pChilds[0] != nullptr)
static_cast<MyNode*>(pChilds[0])->traverse();
}
}
As can be seen I have to do a cast from the base class to the derived class. Alternatively I could make all quadtree related classes templates but I really don't want to do that. I also cant use use boost. Besides that boost::any and similar solutions with RTTI or dynamic casting are to slow since the quadtree is a performance critical component and must to run as fast as possible!
Is there any possebility to maintain the speed of the static_cast while adding some type safety? (a quadtree will only contain nodes of a single type).
回答1:
I know you said you don't want to use templates, but this sort of thing is exactly what templates are for. By making your node class a virtual class, you force extra overhead on each construction and destruction, as well as expanding the size of the node struct by at least one pointer, which will reduce cache coherence.
Also, refusing to use templates will lead you into a morass of static_casts
and unsafe code. Note, for instance, that if pChilds
points to an array of MyNode
and MyNode
has any member variables, then the subscript operator will invisibly not work properly.
回答2:
Since you said
a quadtree will only contain nodes of a single type
then you can use that assumption to optimize your code. As in, you can assume in the body of MyNode::myTraverse()
that this
's children are all going to be MyNode
and you can safely static_cast
any children to MyNode
s.
However, you may worry about what happens if a bug in your code violates your data structure's invariant that it may only hold elements of one type. This is where conditional compilation can come in handy. Assuming the symbol DEBUG
is defined in debug builds:
#ifdef DEBUG
#define my_cast dynamic_cast
#else
#define my_cast static_cast
#endif
...
void MyNode::myTraverse() {
if(pChilds[0] != nullptr)
my_cast<MyNode*>(pChilds[0])->traverse();
}
This will give you the speed of a static_cast
in your release builds and the run-time type-checking of dynamic_cast
in your debug builds, where speed is not as much of an issue. And chances are that if you're violating your structure's invariant in your release builds, you'll also be doing it in your debug builds, and your debug builds will probably crash (it'll actually be undefined behavior, but most platforms will crash when you dereference a null pointer) with access violation exceptions/segfaults.
Alternatively, you could stick with dynamic_cast
for the time being, and switch to static_cast
once you're ready to release and/or you've done testing which shows that using dynamic_cast
instead of static_cast
results in an unacceptable performance hit.
Edit: And since you're creating a library, make sure that it is very clear to your users that clone()
must return an object with the same type that it was called on, with some examples. This is one of those situations where you can't make sure that another programmer doesn't make mistakes, and you'll just have to trust that they can read comments or documentation.
来源:https://stackoverflow.com/questions/19091961/generic-quadtree