Array of unknown size needing intialisation?

This topic is 3512 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

Hi everyone! I have this structure in my code:
struct GuiInit {
/// The type of element to create
string type;

/// A key/value pair
struct GuiKey {
string key;
string value;
};
//vector<struct GuiKey> values;

/// Array of key/value pairs
struct GuiKey values[]; // invalid - can't have a zero-sized array here!
};


What I want is to be able to do a similar thing to this:
	D3DVERTEXELEMENT9 vDeclaration[] = {
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
{ 0, 20, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },
{ 0, 28, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },
{ 0, 40, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
D3DDECL_END()
};


The reason is that GuiInit defines one GUI element that needs to be created, and the key/value pairs are different parameters for it (such as textures or sizes). I'd like to be able to put it all into one array declaration and use it in my code like that, but the closest I'm able to get is using std::vector (as seen in the first source snippet) which won't do what I need it to. Am I trying the impossible?

Share on other sites
Quote:
 Am I trying the impossible?

I don't really know what you are trying...

Are you attempting this in C or C++? By the use of std::vector and what looks like std::string I would say C++, but you also use structs in a very C like way. Either way, there are much better ways to do what you are trying to do I am assuming. For instance if you are using C++ it really looks like you want an std::map to me.

EDIT: Also if you do not know the scores ahead of time you are going to want to look at dynamic memory allocation

GuiKey * values;int the_size;//get sizevalues = new GuiKey[the_size];delete[] values;

Share on other sites
Why not store your key/value pairs in a data structure designed to hold key/value pairs, like a std::map or stdext::hash_map?

Share on other sites
Yes, I'm doing this in C++. I don't want to create a whole new class just for this one concept, though.

What I'd like is to be able to do something like this:
struct GuiInit initialisers[] = {    { "type", { { "key1", "value1" }, { "key2", "value2" } } }};

But I don't think it's possible - am I right?

EDIT: Driv3MeFar, you posted while I was typing this reply - I'll look into them. The main problem is that I need to retrieve a key and value for each element and pass it to a function.

Bascially, the "key" is the parameter to set, and the "value" is the value to set the parameter to. This structure is meant to simplify initialisation code by letting me use a couple of for loops instead of creating each element manually.

Share on other sites
Ok, I've settled on this...

Structure:
struct GuiInit {	/// The type of element to create	string type;	/// A key/value pair	struct GuiKey {		string key;		string value;	};	/// Position of the element	RECT pos;	/// Key/value pairs for GuiElement::SetElementValue	map<string, string> values;};

Initialisation stuff:
	// initialise all the elements	struct GuiInit init[] = {		{ "Sprite", { m_d3d->GetWidth() - 512, m_d3d->GetHeight() - 128, 512, 128 }, map<string,string>() },		{ "END", { 0, 0, 0, 0 }, map<string,string>() },	};	// setup the sprite information	init[0].values["texture"] = "Assets/UI/Texture/victor.tga";	// create elements	int i = 0;	while( true )	{		if( init.type == "END" )			break;		GuiElement* elem = GuiElement::Factory( init.type.c_str() );		if( elem )		{			elem->InitialiseElement( init.pos );			map<string,string>::iterator z;			for( z = init.values.begin(); z != init.values.end(); ++z )				elem->SetElementValue( z->first, z->second );			m_elements.push_back( elem );		}		i++;	}

This all feels really hacky - any suggestions to fix it up would be appreciated.

Share on other sites
I'll re-iterate what has already been said- Sounds like you want std::map...
Personally I love using maps (maybe too much).

std::map<std::string, std::string> Keyset;
std::map<std::string, std::string>::itorator KeySetItor;
std::string MyValue

KeySetItor = Keyset.find("my key");
if (KeySetItor != Keyset.end())
MyValue = KeySetItor->second;

...something like that should get you started.

Sry - you posted that chunk of code wile I was typing-

To me it is un- obvious as to what you are doing in the second code chunk... could you elaborate some more on your intent?

Share on other sites
Would it not be easier to read this data from a file?

Share on other sites
Quote:
 EDIT: Driv3MeFar, you posted while I was typing this reply - I'll look into them. The main problem is that I need to retrieve a key and value for each element and pass it to a function.

And you didn't read mine? What is going on in your code? I really think you should re-think your design.

Quote:
 I don't want to create a whole new class just for this one concept, though.

Why not?

Share on other sites
Quote:
 Original post by rip-offWould it not be easier to read this data from a file?

Yes, actually, it would. I'll have to look into reading XML files now - they're structure complements the task quite nicely.

My main worry is that this is for the two GUIs in the game (HUD and main menus) and I don't want them to be editable without having the actual game code. I could just use resources and embed the XML data into the executable (at least, if I remember correctly), couldn't I?

EDIT: @Portmanteau: I didn't realise you said "For instance if you are using C++ it really looks like you want an std::map to me.". I just skimmed over your post to be honest and saw the dynamic allocation stuff and figured it wasn't what I was looking for.

And I don't want to create a whole new class because I'm being a lazy coder at the moment :/. If I think about it for a bit I'm actually realising a class for these things would be much easier than what I'm attempting here.

Share on other sites
Quote:
 I could just use resources and embed the XML data into the executable (at least, if I remember correctly), couldn't I?

I believe you can, though I've never used resources.

Share on other sites
Quote:
 Original post by vs322To me it is un- obvious as to what you are doing in the second code chunk... could you elaborate some more on your intent?

The second chunk goes through that "init" array and creates all the elements it defines. GuiElement::Factory takes a string and returns the correct class for that string (ie "Sprite" gives a GuiSprite object).

If it gets a valid element then it initialises it and sets all its parameters from the std::map "values" member.

Share on other sites
For what your doing I would recommend tinyXML for now- don't worry about if it is editable by your user yet.

They way I have done it is the first attribute of the xml element is a string that defines its type- I then pass this string to a abstractfactory like what you are doing. Each of my the objects the factory makes have "LoadFromXML" function that takes the curent XML node as its argument. It then only loads / tries to load the data related to itself.

Take a look at "Encode C++ state as XML" in the tinyXML tutorial: here
to see if its what you want.

Share on other sites
you're using C++, but why do you use C compatability constructs like "struct StructName"?

simplest way I can think is to have an initialisation function

class GuiInit {public: std::vector<GuiKey> values;}void GuiInit::GuiInit(GuiInit::GuiKey keys[], int count) {  for(int i=0; i<count; i++) {    this->values.push_back(keys);  }}

really, you should be using a stdext::hash_map or std::map, I would suggest that the method I just proposed is less preferential to:

class GuiInit {public:  //typedef stdext::hash_map< std::string, std::string > map_type;   typedef std::map< std::string, std::string > map_type;   map_type keys;  struct GuiKey {      GuiKey(std::string k, std::string v) : key(k), value(v) {}    std::string key, value;  };  GuiInit(const char* keyValuePairs[][2]) {    for(int i=0; keyValuePairs[0] != NULL; i++) {      add(keyValuePairs[0], keyValuePairs[1]);    }  }  // you should really be returning an std::pair<,>   GuiKey forKey(const std::string& key) const {    map_type::const_iterator k = keys.find(key);    if( k == keys.end() ) return GuiKey("", "");    return GuiKey(k->first, k->second);  }    void add(const std::string& k, const std::string& v) {    keys.insert( std::pair<std::string, std::string>(k, v) );  }};int main() {        const char* keys[][2] = {        {"a", "hello"},        {"b", "world"},        {"c", "C++ isn't C"},        {NULL, NULL},    };        GuiInit init(keys);    return 0;}

Share on other sites
Quote:
 Original post by pcmattmanOk, I've settled on this...

1) Don't use a sentinel value just because someone else did. You don't need it here; you can get the array length using sizeof(array) / sizeof(element).

2) Although you really should be storing the structs in a proper container, such as a vector. To get nice syntax for initializing the vector, use boost::assign and friends.

3) In C++, structs can and often should have constructors. Use this to your advantage so that you don't have to declare the blank map every time.

4) You can make use of operator chaining to put the data into the map.

5) Don't use initialization methods and 'set' calls to initialize an object if you don't really need it. In general, we don't like two-phase construction because it's complicated, error-prone and leaves the object temporarily in an invalid state.

typedef std::map<std::string, std::string> args_t;// Represents data used by the GuiElement factory to construct GuiElements.struct GuiInit {	// Exact derived class to instantiate.	string type;	// Position of the element	RECT pos;	// Additional data passed in the constructor.	args_t values;	GuiInit(const std::string& type, const RECT& pos): type(type), pos(pos), values() {}	GuiInit& operator()(const std::string& key, const std::string& value) {		assert values.find(key) == values.end(); // don't overwrite!		// If your data could come from a file, you should probably		// convert that into an exception instead. Or maybe you'd prefer		// to just let it pass, depending...		values[key] = value;		// Possibly add additional logic here to validate values.		return *this; // the magic part for operator chaining.	}};// The factory does something like:Sprite::Sprite(const RECT& r, const args_t& args): m_rect(r), m_data(args) {}Placeholder::Placeholder(const RECT& r, const args_t& args): m_rect(r), m_data(args) {}GuiElement* GuiElement::Factory(const GuiInit& prototype) {	const RECT& r = prototype.pos;	const args_t args = prototype.values;	if (prototype.type == "Sprite") {		return new Sprite(r, args);	} else if (prototype.type == "Placeholder") {		return new Placeholder(r, args);	} // etc.}// A more sophisticated version would build a map of strings to// creation-functors ahead of time, look up the creation-functor and invoke it// to make the 'new' call.RECT make_rect(int x, int y, int w, int h) {	RECT result = {x, y, w, h};	return result;}// Initialise all the elements// In C++, we don't need the 'struct' keyword to declare an array of structs;// nor do we use the 'typedef struct idiom'. But anyway, I'm going to use// a vector instead.int w = m_d3d->GetWidth();int h = m_d3d->GetHeight();// More magic: boost::assign::list_of uses operator chaining itself to build up// the list of things that are put into the vector, where each is a GuiInit// instance that possibly has other stuff added via its built-in operator// chaining.std::vector<GuiInit> init = boost::assign::list_of	(GuiInit("Placeholder", make_rect(w - 512, h - 128, 512, 128)))	(GuiInit("Sprite", make_rect(w - 512, h - 128, 512, 128))("texture", "Assets/UI/Texture/victor.tga")("awesome", "True"))	(GuiInit("Sprite", make_rect(w - 512, h - 128, 512, 128))("texture", "Assets/UI/Texture/mark.tga"));// Now the awesome part: create elementsstd::transform(init.begin(), init.end(), std::back_inserter(m_elements), GuiElement::Factory);

Share on other sites
Thanks a lot for this information - I'll work at rewriting this code and post back with my solution later.

As for XML support - I'll get this working and then integrate loading from XML files. Thanks for the pointer to tinyxml, I was planning on using that (as I have already used it in the past).

EDIT: So far so good - using std::transform and boost::assign like that works a treat! Thanks guys!

EDIT 2: Any better? Opinions, anyone?
	// width/height of the screen	int w = m_d3d->GetWidth();	int h = m_d3d->GetHeight();	// list initialiser	vector<GuiInit> myinit = boost::assign::list_of(		(GuiInit("Sprite", make_rect( m_d3d->GetWidth() - 512, m_d3d->GetHeight() - 128, 512, 128 ) )("texture", "Assets/UI/Texture/victor.tga"))		);	// create the elements	transform( myinit.begin(), myinit.end(), back_inserter( m_elements ), GuiElement::Factory );

[Edited by - pcmattman on July 9, 2008 6:50:47 AM]