What's a good safe way to initialise memory for types I don't yet know about?
I started thinking about this after receiving an answer for this question. This is a bit tricky to explain, but I'll do my best.
I'm building a small(ish) 2D game engine. There are certain requirements that I need to satisfy, since this engine has to "work" with existing code that others have written for a different engine. Some change to existing code is inevitable, but I want to minimise it.
Users of my engine need to define entities called "gadgets". These are basically struct
s containing shapes and other state variables. These "gadgets" fall into classes, e.g. they may decide to define an icon
gadget or a button
gadget - or whatever.
They will also define a message handler for that class of gadgets.
E.g.
typedef struct
{
shape shapelist[5];
int num_options;
}interface;
static void interface_message_handler( interface * myself, message * msg )
{
switch( msg->type )
{
case NEW_MSG:
{
interface_descriptor * desc = msg->desc;
// initialize myself with contents of this message.
...
}
break;
....
}
}
Users have already given me the corresponding message handler开发者_高级运维 function and also the number of bytes in a interface
object. And they can then ask the engine to create new instances of their gadgets via IDs e.g:
engine->CreateNewGadget( interface_gadget_class_ID, welcome_interface_ID );
where interface_gadget_class_ID
is the ID for that class of gadgets and welcome_interface_ID
is the instance ID. At some point during CreateNewGadget
I need to a) allocate memory to hold a new gadget and then call the gadget class's message handler on it, with a NEW_MSG so that it can initialize itself.
The problem is, if all I'm doing is allocating memory - that memory is uninitialized (and that means all the struct
members are uninitialized - so if interface
contains a vector
, for example, then I'm going to get some wierd results if the message handler does anything with it ).
To avoid wierd results caused by doing stuff to unintialized memory, I really need to call a constructor for that memory as well before passing it to the gadget's message handler function.
e.g in the case of interface
:
pfunc(new (memory) interface);
But my question is, if I have no knowledge of the types that users are creating, how can I do that?
// We create a typedef that refers to a function pointer
// which is a function that returns an interface pointer
typedef interface * (*GadgetFactory)(void);
// we'll actually create these functions by using this template function
// Different version of this function will produce different classes.
template<typename T>
interface * create_object()
{
return new T;
}
// This function takes care of setting everything up.
template<typename T>
void RegisterGadgetType(int gadget_type_id)
{
// Get outselves a copy of a pointer to the function that will make the object
GadgetFactory factory = create_object<T>;
// store factory somewhere
}
interface * CreateGadget(int gadget_type_id)
{
// get factory
GadgetFactory factory;
// factory will give me the actual object type I need.
return (*factory)();
}
RegisterGadgetType<S>(2);
CreateGadget(2);
as i see it, you always know because interface_gadget_class_ID
defines the type to create.
you create a base c++ class: (corresponds to class interface
in your example). this base class contains all of data members which are used by every interface
subclass (that is, every gadget).
the base class also declares all methods common to every gadget. example: each gadget is able to receive a call handleMessage
. handleMessage
is pure virtual, because this method is the subclasses' role to fulfill.
then you extend/subclass to support the stuff you have to do with each gadget's specialization. at this point, you add the members and methods specific to each gadget subclass.
CreateNewGadget
serves as a factory for all your subclasses, where the arguments determine which class you will create.
from there, c++ will handle construction/destruction, allocation sizes, etc..
if you're allowing plugins with their own factories in your engine, then you'll need another level, where third parties register their custom types and inherit from your base(s).
here's a simple layout of the interfaces (in non-compiled pseudo code):
namespace MONGadgets {
class t_interface {
protected:
t_interface(/* ... */);
public:
virtual ~t_interface();
/* each subclass must override handleMessage */
virtual t_result handleMessage(const t_message& message) = 0;
};
namespace InterfaceSubclasses {
class t_gadget1 : public t_interface {
public:
t_gadget1(const welcome_interface_ID& welcome);
virtual ~t_gadget1();
virtual t_result handleMessage(const t_message& message) {
std::cout << "t_gadget1\n";
}
/* gadget1 has no specific instance variables or methods to declare */
};
class t_gadget2 : public t_interface {
public:
t_gadget2(const welcome_interface_ID& welcome);
virtual ~t_gadget2();
virtual t_result handleMessage(const t_message& message) {
std::cout << "t_gadget2\n";
}
private:
/* here is an example of a method specific to gadget2: */
void drawShape(const unsigned& idx);
private:
/* here is gadget2's unique data: */
shape shapelist[5];
int num_options;
};
namespace ClassID {
enum { Gadget1 = 1, Gadget2 = 2 };
}
}
/* replaced by virtual t_result t_interface::handleMessage(const t_message&)
- static void interface_message_handler( interface * myself, message * msg );
*/
class t_gadget_factory {
public:
t_interface* CreateNewGadget(const interface_gadget_class_ID& classID, const welcome_interface_ID& welcome) {
switch (classID) {
case InterfaceSubclasses::ClassID::Gadget1 :
return new InterfaceSubclasses::gadget1(welcome);
case InterfaceSubclasses::ClassID::Gadget2 :
return new InterfaceSubclasses::gadget2(welcome);
/* ... */
}
}
};
}
Example code (ignoring my other suggestion, about factories and virtual functions):
typedef struct
{
shape shapelist[5];
int num_options;
} interface;
static void interface_message_handler( void * myself, message * msg )
{
switch( msg->type )
{
case NEW_MSG:
{
interface *self = new (myself) interface;
interface_descriptor * desc = msg->desc;
// initialize myself with contents of this message.
...
}
break;
case OTHER_MSG:
{
interface *self = static_cast<interface*>(myself);
...
}
break;
....
}
}
Then your CreateNewGadget code does:
void *ptr = malloc(some_amount);
msg newmsg;
newmsg.type = NEW_MSG;
// other fields
some_message_handler(ptr, &msg);
// now we have an initialized object, that we can add to our tree or whatever.
The less horrible version is more like this:
struct gadgetinterface {
virtual ~gadgetinterface() {}
virtual void handle_message(msg *) = 0;
};
struct mygadget : gadgetinterface {
void handle_message(msg *m) {
// no need for NEW_MSG, just do other messages
}
};
gadgetinterface *mygadget_factory(some parameters) {
// use some parameters, either passed to constructor or afterwards
return new mygadget();
}
Then we register a pointer to mygadget_factory with the gadget manager, and CreateNewGadget does this:
gadgetinterface *some_factory(some parameters); // that's it!
Where some_factory
is the function pointer that was registered, so in the case of this gadget type, it points to mygadget_factory
.
精彩评论