Exposing object properties in C++
Contents
  • 1. The problem
    • 1.1. Properties
    • 1.2. Trivial solution
  • 2. Self-registering properties
    • 2.1. A baseclass trick
    • 2.2. Sets of properties
    • 2.3. Adding typesafety
  • 3. Code

This article is based on an original idea voiced on the Software Engineering mailing list by Charles Cafrelli.

Written by Bert Peers, feedback very welcome on bpeers@acm.org ! Thanks & enjoy..
Last update : 02/02/2000 09:40

1. The Problem

1.1. Properties

If you're building a game and use an object-oriented approach, you'll end up with a number of objects which all have their own properties. In an RPG an object, like a sword, may have a certain value, a certain weight, and a certain name. The game code could represent this by having an object CSword, which has member fields like GoldValue (a long), Weight (a float), and Name (a string). Obviously, the actual game code will be tightly integrated with these members : trading code will have to know exactly where to look to find out how much an object costs; inventory code will have to know exactly where to find the weight when checking if you can still carry an object. Adding a new property, like "Attack strength" is not a big deal, since you will have to write new game code anyway to deal with the property. Similarly, the fact that you have to recompile the whole thing and change all the places an obsoleted property (say "Weight") is referenced when dropping it from an object, is not a big hassle either, because you would have to go and remove all the Weight-handling code anyway.

The story is different when you're building an editor to set and tune these properties. In the RPG example, it may take a while until you've found the right combination of weight, value and offensive power to make the sword a good gameplay element. The problem this article is about is how you can connect the editor with the object. It seems trivial, so we immediately have an apparent solution for this.

1.2. Trivial solution

So, what's the problem anyway ? First, you'd take the header files in which you declare the object and load an object from disk (or have it imported from a DLL or something). Then you'd use MFC or another GUI builder to build nice dialogs full of properties, one for every member you see in the header file; using data exchanges between the dialog and actual members in the object, you can keep tuning the object, and when you're done, you just save the object to disk again. Variations could be that instead of actually building an in-game-like object, you just open a data file with some numbers representing the object fields and edit those. In any case, you're directly matching the GUI, and thus the editor, with what's declared in the header files.

This works fine, until a property changes. Everytime a property is added, removed, or changes type (say from long to float), the editor has to be modified accordingly by adding, removing or changing the GUI elements, and ofcourse the input/output code of the editor. If you already have the perfect object layout, this may look like a bogus argument; and indeed, if you're building a relatively simple game, or you're building a game that's been done so often that you practically know for sure what properties your objects will have, the rest of the article may be overkill for the project. However, if you're prototyping a relatively new game where properties keep being added and changed, or you're working on an engine for a gametype which may need tweaking by several interested parties, an explosion of editors could result, each for a slightly different version of the engine.

Total coolness would be achieved if the editor could somehow ask the object itself what kind of properties it has, and then present a GUI to edit these properties according to their type : a dial for numerical values, a checkbox for booleans, a textfield for strings. It turns out this is in fact surprisingly simple to do.

2. Self-registering properties

2.1. A baseclass trick

In the source code, each property can be described by using it's name, such as "Weight". Once compiled, this name is lost and the value it represents can only be retrieved by reading out some position in memory. What we need is a way to find this position, given the name, at runtime : specifying the name of the property we look for as a string, should give us a pointer to where the matching property is. This can easily be done :

class CPropertyList
{
public:
	std::string	PropertyName;
	long		&PropertyLocation;

	void	Register (std::string Name, long *Long)			
        { PropertyName = Name; PropertyLocation = Long; }	
};

class CMyStuff : public CPropertyList
{
	long	Weight;
	MyStuff () 
        { 
		static std::string Weight_S ("Weight"); 
		Register (Weight_S, &Weight);
	}
};

Any object which (multiple) inherits from CPropertyList, can now be checked to see if PropertyName matches a given string like "Weight". If it does, then we know that PropertyLocation points to the member of Weight;

CMyStuff Stuff;
if (Stuff.PropertyName == "Weight") *Stuff.PropertyLocation = 5;

The trick here basically is that an object interested in exposing some of it's properties, registers them at construction time; now, any other object can ask such a CPropertyList object what property it has exposed by checking the string.

2.2. Sets of properties

The next step is to map multiple strings to the corresponding variable. Instead of just checking one string to find out what member a CPropertyList exposes, we would search a map for the name of a member we're interested in, and when found look at the corresponding pointer to access that member :

class CProperty
{
public:	
	union 
	{
		long		*Long;
	};
};

class CPropertyList
{
public:
	std::map <std::string, CProperty>	PropertyList;

	void	Register (std::string Name, long *Long)			
        { 
		CProperty &P = PropertyList [Name]; 	
		P.Long = Long; 
	}	
	
	void	Unregister (std::string Name)
	{ 
		std::map <std::string, CProperty>::iterator I = PropertyList.find (Name); 
		if (I != PropertyList.end ()) PropertyList.erase (I); 
	}
};

Now, a CPropertyList-inherited object registers it's longs just as before. An interested client, like the editor, could now use an iterator to find () a member it's interested in, and when found, read/write it. Even more interesting is the use of an iterator to run over every registered member and display an editing control for every long found. This is what the editor should be doing : use an iterator running from start () till end (), using the names found to populate a drop down list. When the user selects a variable from the drop down list, the editor pops up a control in a modal dialog box which allows the user to enter a new value. That value is then stored in whatever location the CProperty matching the string reports.

The important thing to realise here is that this happens completely at runtime. All the editor cares about is that the object being edited is a CPropertyList; only the members of this CPropertyList type are used to inspect and modify the members of the object being edited; that object can be changed and recompiled at will. If the Editor just has a way to get such a new object without recompilation (say using a CPropertyList *Factory () method in a DLL), it will report any new properties just fine without any recompiling. All the modified object has to do is make sure it registers the correct members at construction time; since this is compile-time safe, you cannot register a variable that has been deleted. Consequently, the editor cannot accidentally offer an editor box for a value that has been dropped, and worse yet, continue by writing values at bogus locations; this already removes a whole class of engine/editor versioning problems. At the same time, you get to decide which new members get exposed and which remain hidden, which is not always the case with some approaches based on virtual tricks.

2.3. Adding typesafety

Normally, you'd want to expose more than just longs : booleans, strings, floats etc are typical properties. This can be done trivally by just expanding the CProperty class, and adding Register code :

typedef enum { PropBool, PropString, PropFloat, PropLong, PropInt } TProperty;
class CProperty
{
public:	
	TProperty		Type;
	union 
	{
		bool		*Bool;
		std::string	*String;
		float		*Float;
		long		*Long;
		int		*Int;
	};
};

class CPropertyList
{
public:
	std::map <std::string, CProperty>	PropertyList;

	void	Register (std::string Name, bool *Bool)				
	{ CProperty &P = PropertyList [Name]; 	P.Type = PropBool; P.Bool = Bool; }	
	void	Register (std::string Name, std::string *String)	
	{ CProperty &P = PropertyList [Name]; 	P.Type = PropString; P.String = String; }	
	void	Register (std::string Name, float	*Float)			
	{ CProperty &P = PropertyList [Name]; 	P.Type = PropFloat; P.Float = Float; }	
	void	Register (std::string Name, long	*Long)			
	{ CProperty &P = PropertyList [Name]; 	P.Type = PropLong; 	P.Long = Long; }	
	void	Register (std::string Name, int		*Int)			
	{ CProperty &P = PropertyList [Name]; 	P.Type = PropInt; 	P.Int = Int; }

	void	Unregister (std::string Name)
	{ 
		std::map <std::string, CProperty>::iterator I = PropertyList.find (Name);
		if (I != PropertyList.end ()) PropertyList.erase (I); 
	}
};

The editor queries the CPropertyList as before, but using a switch on the Type reported in the CProperty, it offers a slightly different modal dialog box. More advanced GUI programming could in fact skip the dialog box altogether and just update the form holding the dropdown list and the controls, and enable/disable the appropriate controls when a selection from the list is made (say disabling anything but the boolean check box when a variable is selected which is PropBool).

3. Code

Here is a header file which contains the above code. Just #include it in the header file of an object you want to make a CPropertyList, expose the members you want in the constructor, and you're ready to go.

Homepage