Our Properties: Gamasutra GameCareerGuide IndieGames Indie Royale GDC IGF Game Developer Magazine GAO
My Message close
Contents
Sponsored Feature: Behind the Mirror - Adding Reflection to C++
 
 
Printer-Friendly VersionPrinter-Friendly Version
 

Latest News
spacer View All spacer
 
February 23, 2012
 
Interview: Silver Dollar uses XBLIG for its mad experiments
 
Last year's Supreme Court case on games cost California $1.8M [6]
 
GDC 2012 details Moriarty, Della Rocca, 'Rant' sessions in Education Summit [1]
spacer
Latest Jobs
spacer View All     Post a Job     RSS spacer
 
February 23, 2012
 
2K Games
Public Relations Manager - 2K Games
 
2K Marin
Level Designer
 
2K Marin
Senior Rendering Programmer
 
Zindagi Games
Presentation/Game Programmer
 
The Workshop
Art Director
 
Blizzard Entertainment
Senior Software Engineer, Tools
spacer
Latest Features
spacer View All spacer
 
February 23, 2012
 
arrow Postmortem: Days of Wonder's Ticket to Ride Pocket [1]
 
arrow Sponsored Feature: Canada - Scoring High as a Game Nation [3]
 
arrow The Vita Interview [19]
 
arrow Gamification Dynamics: Identity and Story [7]
 
arrow What Makes Social Games Social? [3]
 
arrow Will Tablet Game Quality Soon Leapfrog Consoles? [27]
 
arrow Tearing Down Barriers: How to Bring MMO Players Together [10]
 
arrow Withstanding the Collapse of the Middle [4]
spacer
Latest Blogs
spacer View All     Post     RSS spacer
 
February 23, 2012
 
Piracy and the four currencies [5]
 
The Secondary Costs of Outsourcing [10]
 
Sixty to Zero [4]
 
The Combinatorial Itch [5]
 
God Games and the Superman Complex [13]
spacer
About
spacer Editor-In-Chief/News Director:
Kris Graft
Features Director:
Christian Nutt
Senior Contributing Editor:
Brandon Sheffield
News Editors:
Frank Cifaldi, Tom Curtis, Mike Rose, Eric Caoili, Kris Graft
Editors-At-Large:
Leigh Alexander, Chris Morris
Advertising:
Jennifer Sulik
Recruitment:
Gina Gross
 
Feature Submissions
 
Comment Guidelines
Sponsor
Features
  Sponsored Feature: Behind the Mirror - Adding Reflection to C++
by Geoff Evans [Programming, Sponsored Feature - Intel]
6 comments Share on Twitter Share on Facebook RSS
 
 
May 17, 2011 Article Start Page 1 of 3 Next
 

[In this Intel-supported Gamasutra article, Geoff Evans explores implementing a reflection system in C++ and how the payoff ultimately outweighs the effort. I worked with Geoff at Insomniac Games where he applied a lot of these ideas.  Reflections allow for an interesting level of interaction between gameplay code and tools (especially the serialization of objects from data files). In a data-driven design, using a classes-own structure to inform behaviors can be very powerful.

This article was originally printed as a Game Developer magazine article in the February 2011 issue. It's worth reading, so here it is on Gamasutra!

- Orion Granatir]

Reflection is a programming language feature that adds the ability for a program to utilize its own structure to inform its behavior. Reflection has its costs, but those are often outweighed by the ability to automate the serialization of objects into and out of a file, cloning, comparison, search indexing, and network replication, type conversion (copying base data between derived class instances), and user interface generation.

Of course, all these tasks can be accomplished without reflection capabilities, but you will likely pay higher costs having to write code that is very rote and prone to error. A good implementation of reflection can provide a platform on which each of these problems can be solved without glue code in every class that desires these features.

At the highest level, reflection can encompass many different features, such as runtime knowledge of class members (fields and methods), dynamic generation and adaptation of code, dynamic dispatch of procedure calls, and dynamic type creation.

However, for the purposes of this article, I will define C++ Reflection to mean "having access at runtime to information about the C++ classes in your program."

RTTI

Before diving headlong into how to add reflection to C++, it's worth noting what type of information is already built-in. The C++ language specification provides minimal information about the classes compiled into a program. When enabled, C++ Run Time Type Information (RTTI) can provide only enough information to generate an id and name (the typeid operator), and handle identifying an instance's class given any type of compatible pointer (dynamic_cast<>).

For the purpose of game programming, RTTI is often disabled entirely. This is because its implementation is more costly than a system built on top of C++. Even if a program only makes a handful of RTTI queries, the toolchain is typically forced to generate, link, and allocate memory at runtime for information about every class in the application (that has a vtable). This significantly increases the amount of memory required to load your program, leaving less memory available for face-melting graphics, physics, and AI. It's better to implement your own RTTI-like system that only adds cost to the classes that need to utilize it. There are plenty of practical situations where vtables make sense without needing to do runtime-type checking.

Thus, the first step in implementation of a reflection system is typically a user implementation of RTTI features. This can be accomplished with only a couple of steps. Type information can be associated by a static member pointer (which also makes a good unique identifier for any given type within the program). In addition, some virtual functions allow querying an object's exact type, as well as test for base class types:

// Returns the type for this instance
virtual const Type* GetType() const;
// Deduces type membership for this instance
virtual bool HasType( const Type* type ) const;

GetType returns a pointer to the static type data, and HasType compares the provided type against its static type pointer as well as every base class' type pointer. This gives us all the information needed to reimplement dynamic_cast<>, but it only adds overhead to classes that are worth paying the added cost of type identification and type checking.

Visitor Pattern

The simplest technique for implementing reflection is to take a purely programmatic approach. Virtual functions can be a mechanism for the traversal of all fields in a class. The visitor design pattern provides an abstraction for performing arbitrary operations on the fields as in Listing 1.

This is a textbook implementation of the visitor design pattern. Objects deliver the visitor to each one of its fields and the visitor gets an opportunity to transact with each field in series. It offers excellent encapsulation since the object does not know or care about any implementation details of what the visitor is trying to accomplish.

This technique does not require data from an external tool to do its job since it's implemented entirely in the code compiled into the program. It's simple to step through and debug, and extensible since many operations can be implemented as another class of Visitor.

With this approach, the development cost is small. A single line of code for each field in every class in your codebase is a fair price to pay to attain the benefits reflection can provide. However, there are some drawbacks with using a visitor function for reflecting upon your objects. There are a lot of virtual function calls happening to interact with each field in a class. This is a concern for performance-critical code, and on certain platforms. Also, this technique is best suited for operations that want to visit every single field of a class. There are many situations where this work is not required, and iterating over every field just to access a few is wasteful and time consuming (depending on the size of the object).

Data Model

To really take reflection to the next level, it's necessary to be able to address specific fields and read and write data without iterating over every field in the class. A data model that represents the classes and fields specified in the code is needed to accomplish this. At runtime, your program can reflect upon this model to interface with objects and their field data.

This data model is owned by a central registry of type information. This singleton object owns all the type information in the program and can have support for finding type information by name. It's also a central point where a map of the entire inheritance hierarchy of classes can be built. The registry can be populated by employing a parser tool to analyze your source code, or by adopting a method similar to the visitor function approach to populate this data model at program startup.

 
Article Start Page 1 of 3 Next
 
Comments

Michael Compton
profile image
Nice to see Greenspuns Tenth Rule is still very much alive.

http://en.wikipedia.org/wiki/Greenspun's_Tenth_Rule

Doug Binks
profile image
:)

But seriously this is a neat approach to general serialization in C++, and the field layout knowledge gained through this approach could also be used to speed up serialization in the case of packed members when the archive layout matches the field layout.

One thing I'm not sure about is how close to this artice the current Helium project's code is to the article - from the blog it seems there were substantial changes when merging Helium with another codebase called Lunar.

Kim Simmons
profile image
Great article! It got me thinking in a few new ways when it comes to saving and loading data.

I'm a bit worried about the use of the uint32_t in the GetFieldOffset function. Member pointers are fairly new to me, but my general knowledge of pointers made me react to how this would work on a 64bit system? Casting 64bit pointers to a 32bit uint sure is trouble, but is this true for member pointers? Since you suggest that it's merely an offset value, I would reckon it has to be a very large class for it to extend past the 32bit range, but still possible to get truncated? Would a void pointer be better?

Martin Steuer
profile image
Interesting read, thanks.

It seems there is a slight mistake in the prototypes for AddField and GetFieldOffset, these should be:


template< class ObjectT, class DataT >
Field* AddField( Class *owner, DataT ObjectT::* fieldPtr, const char* name,
const Class *data = NULL)

template< class ObjectT, class DataT >
uint32_t GetFieldOffset(DataT ObjectT::* field )


The call to GetFieldOffset within AddField should then use fieldPtr as its
parameter.

Mark Taylor
profile image
Michael Compton, have to agree with you. By the time you have boosted and STLed C++ it has become a mess of templates. There is a reason a big C++ project can easily take 10 minutes or longer to compile, particularly when templates are used to patch every shortcoming. The compile times are bottlenecks than can kill a business. You can mitigate the damage by restricting templates to source code, rather than headers, but this makes a simple problem, such as enumerating through a collection, a major headache. The solution is to minimize one's use of C++, to provide the bare minimum of an engine, and put all the important control code in a better language. In a good language you don't micro-mangage iterators, ask any LISP programmer. Software patterns are what imperative language users use for lack of a suitable keyword or operator.

Brian Smith
profile image
@Mark - agreed... as long as that control code isn't in a language that has an RSI-inducing amount of parenthesis :)


none
 
Comment:
 




UBM Techweb
Game Network
Game Developers Conference | GDC Europe | GDC Online | GDC China | Gamasutra | Game Developer Magazine | Game Advertising Online
Game Career Guide | Independent Games Festival | Indie Royale | IndieGames

Other UBM TechWeb Networks
Business Technology | Business Technology Events | Telecommunications & Communications Providers

Privacy Policy | Terms of Service | Contact Us | Copyright © UBM TechWeb, All Rights Reserved.