Specific Data Stored in an Arbitrary-Type Container

posted in Beals Software
Published September 14, 2009
Advertisement
This entry expands on my last one (and now the reply I made to Slather) about my OpaqueData situation (this is the name that XNA uses to store data associated with content that doesn't fit into their standard object model, but needs to be stored for use by processors.)

When I started that idea, there were two intended uses for it: something to compliment WinForm's Tag property in controls and the above stated OpaqueData. I'm going to use the Tag property in this entry because it covers both situations that the system is intended for whereas OpaqueData only covers one.

Now, before I start, I want to point out that I still consider myself a newb when it comes to C#. I've made one assumption in the following that I haven't looked into (simply because, for my solution, it doesn't matter.) The assumption being that C# seems to store object members as references. So, passing MyObject instantiates a clone of the object, but the clone's properties are references to the original object. For example:
using System;namespace Lab{    class Program    {        class MyObject        {            public string Name;            public MyObject()            {            }            public override string ToString()            {                return String.Format("Name: {0}", this.Name);            }        };        static void Foo(MyObject Obj)        {            Obj.Name = "Programmer16";        }        static void Foo(string String)        {            String = "Programmer16";        }        static void Foo(ref string String)        {            String = "Programmer16";        }        static void Main(string[] args)        {            MyObject Obj = new MyObject();            Foo(Obj); // Name = "Programmer16"            Console.WriteLine(Obj);            Obj.Name = ""; // Name = ""            Foo(Obj.Name); // Name = ""            Console.WriteLine(Obj);            Foo(ref Obj.Name); // Name = "Programmer16"                Console.WriteLine(Obj);            Console.In.Read();        }    }}

So, the type passed in is not a reference itself, but it's members are. Anyway, this is kind of the idea behind the system.

So, with that out of the way, a full definition of what I wanted:
An object that could store specific information, no matter the type, and allow access to said information.

However, the information stored needs to be able to be of two different types: copy of data or reference to data (via pointers.)

An example: Lets take a TreeView and a TreeViewNode control from a game editor made with my imaginary GUI. The TreeView is an object browser in the editor, and thus lists different types of objects, separated by root nodes. Each rood node contains a specific type of item. Each of the TreeViewNode would have a Tag member that the editor's code sets to the item associated with it.

Two possible choices:
1) Store the tag as a copy of the data and store a pointer to the TreeViewNode in our item classes, so that when the item is changed, it can update the node (for example, if the item's name is changed, we'd want to update the node's text).
2) Store the tag as a reference to data. For the name changing example, if the TreeView had LabelEdit enabled, we would override the node's OnLabelChange and it would change's it's own text as well as the item's name. Viola, no need for an edit item dialog to change it's name; we just edit the label and move on.

However, there are situations where we would want to store an object as a copy of an object, yet still be able to have the information persist between retrievals. For example, say you have a TreeViewNode that represents a weapon. The editor requires that every weapon node keep a hierarchy list of type WeaponHierarchy (which, in this example, contains a reference to the weapon it's associated with and, if available, the item(s) it was derived from.) Seeing as how only the editor cares about this information (our Weapon class is part of the game engine itself and we don't want to have to create EditorWeapon), we can create a temporary object and store that in the node's Tag member. Our tag member now contains arbitrary data of a specific type and will persist until the node is destroyed or the Tag member is changed.

I hate to be repetitive, but the idea here is that we're using a non-template object to store data that would normally be either static or templated. The type of data that a specific object contains should never change (unless it changes EVERYWHERE; i.e. between the different states of a game or editor or something.) Normally we would use a specific class (i.e. derive WeaponTreeNode from TreeViewNode and add the WeaponHierarchy member) or use a templated class (TreeViewNode is no longer ArbitraryData, but a type defined by T.) However, in the situations this system is intended for, neither of these would suffice (a static type means that TreeViewNode has a specific purpose, which it doesn't and a templated class would mean that every TreeViewNode in the tree would have to contain the exact same type of data, which is usually never the situation.)

So, setting the data to an int in one function, then to a vector in another, is not the intended use. We're storing state-specific, type-specific data, in an arbitrary data container. I say state-specific because, as long as states are well-defined and everything adheres to each state, changing the type of data is perfectly acceptable. State-specific information is very useful in several situations; for example (as I love to do), making your engine double as a built in, run-time editor. Every tile in the "CurrentMap" class could store engine-specific data when in play mode, but store editor-specific data when in edit mode.

Anyway, I could go on and on, giving different examples, but I'm not going to. Here's the code for the class (named TagItem at the moment.) It's a pretty simple wrapper that stores the values passed in. The only thing really needed is the CastTo<>() method. It attempts to cast the value to a pointer-to-pointer of ValueType, and, barring failure, attempts to cast it to a regular pointer of ValueType. Successful casting (for either attempt) returns a pointer. This way, it doesn't matter to me whether I'm manipulating a reference or a copy.[1]

namespace dbeals{    /*    TagItem is an arbitrary container for storing state-specific, type-specific data as either copies of objects or references to objects.    To store a copy use either:        TagItemInstance = ObjectInstance;    or        TagItemInstance = *ValidObjectPointer;    To store a reference use either:        TagItemInstance = &ObjectInstance    or        TagItemInstance = ValidObjectPointer;    Do NOT store references to temporary. I'm not sure how defined this is (if at all.) I only tested it with a vector, which resulted in an empty vector after the object went out of scope,    but I don't think that's reliable behavior.    */    struct TagItem    {        boost::any Data;        // Default Constructor        TagItem()        {        }        // Copy Constructor        TagItem(const TagItem &Tag) : Data(Tag.Data)        {        }        // Value Constructors        template         TagItem(const ValueType &Value) : Data(Value)        {        }        template         TagItem(ValueType *Value) : Data(Value)        {        }        // Modifiers        void Clear()        {            this->Data = boost::any();        }        void Swap(TagItem &Tag)        {            this->Data.swap(Tag.Data);        }        // Queries        bool IsEmpty() const        {            return this->Data.empty() == true;        }        bool IsNotEmpty() const        {            return this->Data.empty() == false;        }        // Removed : CanCastTo<>()        /* I removed this because it just bulks things up. The whole design is to be simple using the form:        ValueType *Result = 0;        if((Result = Tag.CastTo()) != 0)            // Do something here        This requires only one cast, whereas using CanCastTo<>() would require two casts.        To use this class in that manner (not for which it was intended, but inherently can be use in such a manner), just use multiple casts:        ValueType0 *Result0 = 0, ValueType1 *Result1 = 0;        if((Result0 == Tag.CastTo()) != 0)            // It's ValueType0, do something with it.        else if((Result1 == Tag.CastTo()) != 0)            // It's ValueType1, do something with it.        or        if(Tag.CastTo())        {            ValueType0 *Result = Tag.CastTo();            // do something with it.        }        else if(Tag.CastTo())        {            ValueType1 *Result = Tag.CastTo();            // do something with it.        }        */        // Accessors        template         ValueType *CastTo()        {            ValueType **Result = boost::any_cast(&this->Data);            if(Result)                return *Result;            ValueType *Result2 = boost::any_cast(&this->Data);            if(Result2)                return Result2;            return 0;        }        template         const ValueType *CastTo() const        {            return const_cast(this)->CastTo();        }        // Assignment Operators        TagItem &operator =(const TagItem &Pointer)        {            this->Data = Pointer.Data;            return *this;        }        template         TagItem &operator =(const ValueType &Value)        {            this->Data = Value;            return *this;        }        template         TagItem &operator =(ValueType *Value)        {            this->Data = Value;            return *this;        }    };}


Where I'm looking for input:
Everywhere, of course. However, more specifically:
1) My const version of CastTo<>(). I hate doing it the way I have at the moment, because if, for some retarded reason or because of a programmer-error, the non-const version changes the value, it could causes some major issues and hard to track down bugs (i.e. the thinking "I'm calling a const version; so it must be memory corruption or something causing my data to change".)
I took out the code I was attempting to use because I evidently used undo and it's not what I was using (I copy-pasted the non-const and modified it to be const.) The first line attempted to cast to const ValueType ** and used the template argument ValueType *, but constantly reported the error that it could not cast from 'dbeals::string **' (a typedef for std::string) to 'basic_string *'. Pretty much no matter what I did, I got that exact same error.

2) Most importantly, is this really a viable solution? It works exactly how I want it to in all of my tests, but I'm not positive it's even a good idea (especially the CastTo<>() method.)

[edit]
3) How to get around the issue of string literals. Passing in string literals is (well, seems to be) completely safe. You can only retrieve it using CastTo(), so the returned pointed-to-object cannot be modified, but is this defined behavior? I guess there's not really a problem with it, as long as it is defined behavior.

[1] - This kind of breaks the design, because, by my own definition, I should know whether or not it's reference or copy data. However, I left it as this for the simple fact that it's less complex. Yes, I should know what I'm dealing with, but my interaction with the data shouldn't change depending on whether or not it's a reference or a copy. Splitting it into two different functions (as my original pass last night did), causes me to have to change a lot of code if I decided to pass something by reference rather than a copy.

Anyway, any comments, suggestions, etc, etc are welcome. Also, looking for a good name for it. I was thinking ArbitraryDataContainer or some-such, but I'm not sure. I'm leaving it as TagItem for now.
Previous Entry Untitled
Next Entry dbeals redesigned
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement