Smallest possible useful C++ Attribute system?

Over the years, I’ve seen almost as many attribute reflection systems as I’ve worked on games, and that’s a lot of games. This article by Gary McNickle on Gamasutra is clean and easy to read, but it is not as small as possible. It inspired me to write a few notes about how I think these things should be put together.

Here are the usage goals for the system:

  • The game code owns the variables, not the attribute system
  • The attribute system does not force the game to use it
  • Attributes can be searched, and iterated over

Out of scope:

  • Serialization to disk, and relocation
  • Implementation of features normally part of C++ or the STL

Here are some technical requirements I decided to hit:

  • Only one copy of any name of any variable would ever be stored
  • As much as possible the system will self-priming
  • The system will be typesafe
  • Methods on the class may be templated for convenient type coercion
  • Avoid “best practices” such as accessors if they don’t make the code simpler

I tried several variations, and each one got shorter and less complex. I started with a template meta-programmed solution, not unlike the cleverness referenced by Gary in the Boost spirit library. The next version stripped this down to a much simpler templated attribute system; I simplified that through two rewrites. At first I pursued a template based system because the code tends to be easier to read than piles of preprocessor mess. I have a bit of a distaste for macro based systems because they tend to become unwieldy and difficult to maintain as features get wedged into the system, whereas template code remains legible. After some thought it occurred to me that if one kept the attribute system trivially simple, the macros could remain trivially simple. Templates could be used at a higher level than the base objects to implement the complexity. At this point, I overcame my built-in distaste for macro-based attribute systems, and came up with the smallest useful system I could think of.

In real life, I would not use std::string, but rather a block of data containing the char data, and indexed by offsets. Blocks of data indexed by offsets are easy to relocate to keep memory compact, and are trivial to cache of to disk. This approach also avoids the constant overhead of a string object (which tends to be 32 bytes), and also avoids any small allocations from the heap (a string takes two).

Also, I would think carefully about the use of std::map, to make sure it does not impact run time performance. A sure sign that an STL map of std::string is a poor choice is if profiling shows a measurable amount of time being spent in string comparisons.

I wonder if this can be made smaller?

 

#include <typeinfo.h>
#include <string>
#include <map>

class AttributeBinder
{
public:

    class AttributeDescriptor
    {
    public:
        AttributeDescriptor() : ti(0), off(0) { }

        AttributeDescriptor(const char* name, const std::type_info& ti, ptrdiff_t off) :
            name(name), ti(&ti), off(off) { }

        AttributeDescriptor(const AttributeDescriptor& rhs) :
            name(rhs.name), ti(rhs.ti), off(rhs.off) { }

        std::string name;
        ptrdiff_t off;

        bool typeEqual(const AttributeDescriptor& rhs)
        {
            return *ti == *rhs.ti;
        }

    private:
        // note, must compare type infos by *m_pInfo == *rhs.m_pInfo
        // because the pointers might not be the same, even if the types are
        const std::type_info* ti;
    };

    void bind(const char* varname, const std::type_info& ti, ptrdiff_t offset)
    {
        attribs[varname] = AttributeDescriptor(varname, ti, offset);
    }

    std::map<std::string, AttributeDescriptor> attribs;
};


// Within a class, create a binding object, and start the Bind method.
// Note that it is valid to put other code between BIND_START and BIND_END
// if you want it to run during the static Bind initialization.
// Semicolons are optional after BIND_START, you can use them if you use
// a pretty-printer on code and want things to line up nicely

#define BIND_START \
    static AttributeBinder binding; \
    static void Bind() \
{ \

// Bind a single variable

#define BIND(c, v, t) \
    binding.bind( #v, typeid(t), (ptrdiff_t)&((c*)0)->v)

// End the bind method
// Semicolons are optional after BIND_END, you can use them if you use
// a pretty-printer on code and want things to line up nicely

#define BIND_END \
}

// Instantiate the binding object for class C, and make a globally
// instantiated struct whose constructor will invoke C's Bind method
// which has been declared in BIND_START
// This goes in the implementation file, in the same namespace as the
// class C.

#define BIND_ATTRIBUTES(c) \
    AttributeBinder c::binding; \
    namespace { \
        struct Static ## c ## Binder \
        { \
            Static ## c ## Binder() { c::Bind(); } \
        }; \
        static Static ## c ## Binder static ## c ## binder; \
    }

And here’s an example of decorating a class:

 

class Foo
{
public:
    BIND_START;
    BIND(Foo, myInt,    int);
    BIND(Foo, myFloat,    float);
    BIND(Foo, bar,        float);
    BIND_END;

    Foo() : myInt(1), myFloat(2), bar(3)
    {
    }

    int myInt;
    float myFloat;
    float bar;
};

BIND_ATTRIBUTES(Foo);

 

Finally, to look up a variable, you would use your privileged knowledge of the implementation to do something like the following:

    Foo myFoo;
    // looking up myInt
    std::map<std::string, AttributeBinder::AttributeDescriptor>::const_iterator i =
        Foo::binding.attribs.find("myInt");

    // reading the int
    int intVar = *(int*) (ptrdiff_t)&myFoo + i->second.off;

    // writing the int
    *((int*) (ptrdiff_t)&myFoo + i->second.off) = 3;

 

This can be prettied up with simple templated member functions, but I leave that as an exercise to the reader.

Post to Twitter Post to Delicious Post to Facebook

  • admin
    Note, further correspondence is here: http://www.realityprime.com/articles/the-audience
  • admin
    Adding a vector of base classes to the AttributeBinder, and a macro to declare them allows for reflection of singly-inherited classes. A scheme like this one works with single inheritance because subclass variables are simply added at the end of the base class. It doesn't work with multiple inheritance because the calculation of the offset of the base is more complex. It could be resolved with dynamic_cast, but I'm moving this framework in the direction of not needing RTTI. Other schemes that would work with multiple inheritance introduce complexity, or overhead, and I'm not satisfied introducing either.

    Add to AttributeBinder:

    std::vector<attributebinder> bases;

    New macro:


    #define BIND_BASE(c) \
    binding.bases.push_back(&c;::binding);


    Example to extend Foo in the original post:

    class Baz : public Foo
    {
    public:

    BIND_START;
    BIND_BASE(Foo);
    BIND(Baz, bool1, bool);
    BIND(Baz, int2, int);
    BIND(Baz, float3, float);
    BIND_END;

    Baz() : bool1(true), int2(2), float3(3) { }

    bool bool1;
    int int2;
    float float3;
    };</attributebinder>
  • admin
    Hi Alexey, useful comments! I have updated the AttributeBinder with your third point, here:

    http://meshula.net/wordpress/?p=174

    There are several reasons that many programmers consider type_info evil for games. I suppose the most glaring one is that it is incompletely implemented or non-existant on consoles. The second most glaring reason would be the reason multiple inheritance is considered bad; resolution can involve searching a vtable and doing matching. I wanted to stay away from roll your own RTTI because I wanted to keep this example really simple; but roll your own RTTI is actually pretty simple, so I should probably just go ahead and do it. I single stepped through the RTTI comparison code and discovered that MSVC generates code that strolls through the vtable, and ultimately does a strcmp against the type names it discovers (I didn't dig deep enough to discover if the type name is baked into the code, or if it is constructed dynamically, which would be trebly 3vil). Vtable strolls and strcmps in game code freak me out, so I'm sold on not using built-in RTTI.

    I'm still thinking of some light way to do polymorphic reflection. I'll post again when I have something I like.

    Suggestions are welcome!
  • Alexey
    Hi, Nick!
    While I hardly imagine a code smaller than yours, I wonder if you can get rid of some disputable moments.
    As far as I know, using std::type_info implies that compiler generated type information for polimorphic classes (/GR option for Microsoft complilers) which is considered evil by many programmers in the industry.
    For inherited classes we need to extend functionality to take parent's attributes into account.
    And by the way, do we really need to copy attribute names (twice in our case)? I think we can just leave them in static storage (probably we need to declare custom sort predicate for the map container in that case)...
blog comments powered by Disqus

Bad Behavior has blocked 804 access attempts in the last 7 days.