I'm going to implement something like the Unity3D component model for my C++ game engine. Please, review my draft of the Component
base class:
class Component
{
public:
template<class T>
static T* create(void)
{
if (Type<T>::id == 0)
Type<T>::id = ++_idCounter;
T* component = new T();
component->_id = Type<T>::id;
return component;
}
template<class T>
inline bool is(void) const
{
return Type<T>::id == _id;
}
protected:
Component(void) {}
private:
static unsigned int _idCounter;
unsigned int _id;
template<class T> struct Type { static unsigned int id; };
};
unsigned int Component::_idCounter = 0;
template<class T> unsigned int Component::Type<T>::id = 0;
It has some RTTI-like functionality, and I can use it this way:
class ComponentA: public Component {};
class ComponentB: public Component {};
...
ComponentA* a = Component::create<ComponentA>();
ComponentB* b = Component::create<ComponentB>();
std::cout << a->is<ComponentA>() << std::endl; // 1
std::cout << b->is<ComponentB>() << std::endl; // 1
std::cout << b->is<ComponentA>() << std::endl; // 0
std::cout << a->is<ComponentB>() << std::endl; // 0
And I have a question. How can I prevent any construction of any descendant of Component
outside of Component::create
?
1 Answer 1
Sorry, but this is simply not a good design.
Your reliance on memory-owning raw pointers means that the library is easy to use wrongly, and that the user will leak memory. This is a big no-no in modern C++. First, because it’s harmful and makes user code brittle and complex; and second, because it is unnecessary. Modern C++ gives you all the tools to avoid manual memory management.
Think carefully about ownership in your component model and choose an appropriate smart pointer. Since you control the object hierarchy, boost::intrusive_ptr
may be the best match, but consider other alternatives which are less generic (such as std::unique_ptr
).
Finally think about ways to avoid pointer use completely, if possible. In fact, the code you’ve shown doesn’t use runtime polymorphism at all so there is no use of pointers. Just handle objects directly.
Regarding your implementation of RTTI: Without knowing the detailed requirements of your object model it’s hard to comment here; however, you say that you don’t want to get the RTTI overhead. What makes you think C++ RTTI has more overhead than your solution? In fact the implementation you’ve shown is, if anything, less efficient than C++ RTTI because your use-case can be resolved at compile-time.
But let’s assume that you just forgot to make your classes polymorphic, and that the real code cannot resolve the type information at compile time. Then your RTTI mechanism stores redundant information and is still less efficient than C++’ built-in mechanism.
Finally, if you are afraid of RTTI overhead (and have data to back this up) then the real solution is not to roll your own system, it’s to avoid RTTI be redesigning your classes such that they have a uniform interface and don’t need to be distinguished at runtime. In fact, this is the idiomatic solution in C++, and RTTI is the exception rather than the norm.
idCounter_
. \$\endgroup\$