Jump to content

  • Log In with Google      Sign In   
  • Create Account

Multiple types in a map container C++


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
12 replies to this topic

#1 Soundstone   Members   -  Reputation: 191

Like
0Likes
Like

Posted 22 April 2013 - 12:40 PM

Hi everyone, 

 

not sure if this is the proper thread to post this question to, if not I apologize and will gladly post it where it needs to be. I am having an issue with understanding how to use multiple data types in one map container. For example my code looks like this:

 

 

struct A
{
    string mName;
    int    mNumber;
};
 
struct B
{
    string mName;
    float  mFloater;
    bool   mFlag;
};
 
 
class C
{
     C();
     ~C();
 
//basically I want this map to hold a list by reference int for the entire collection of both objects from A and B.
map<int, A/B> myMultiMap;
 
}
 
so that in main..
 
int main()
{
     C* myC;
     A* myA;
     B* myB;
 
    myC->myMultiMap[0] = myA;
    myC->myMultiMap[1] = myB;
}
 
is there any easy way to do this. I haven't had much luck with getting the boost libraries to load correctly, I've only been at this about a year, and don't completely know how to do some of the things. I appreciate any help that is offered, even ifs its just a link or something. Thanks for reading. 
 
Regards,
Soundstone


Sponsor:

#2 ApochPiQ   Moderators   -  Reputation: 16401

Like
0Likes
Like

Posted 22 April 2013 - 12:50 PM

Here's one option:
struct Base
{
    virtual ~Base() { }
};

struct Alpha : public Base
{
    int Foo;
};

struct Beta : public Base
{
    std::string Bar;
};

int main()
{
    Alpha* someAlpha = new Alpha;
    someAlpha->Foo = 42;

    Beta* someBeta = new Beta;
    someBeta->Bar = "Test";

    std::map<int, Base*> someMap;
    someMap[0] = someAlpha;
    someMap[1] = someBeta;
}
A better design would be to use std::shared_ptr (or boost::shared_ptr if your compiler doesn't support the C++11 standard) instead of raw pointers.

#3 Paradigm Shifter   Crossbones+   -  Reputation: 5435

Like
0Likes
Like

Posted 22 April 2013 - 12:55 PM

You still need some way to know what type of object it actually is though... you can use dynamic_cast for that if you want.

 

Can you only have one of type A or type B corresponding to a particular int key? Because if that is not the case you are probably better off having 2 maps, one holding A's and the other holding B's.


"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

#4 ApochPiQ   Moderators   -  Reputation: 16401

Like
4Likes
Like

Posted 22 April 2013 - 03:53 PM

You still need some way to know what type of object it actually is though... you can use dynamic_cast for that if you want.

I disagree.


In a well-designed system, you should use virtual dispatch to "do things" with the Alpha/Beta objects. You should virtually never need to know its concrete type, and dynamic_cast is a code smell.

#5 Paradigm Shifter   Crossbones+   -  Reputation: 5435

Like
0Likes
Like

Posted 22 April 2013 - 03:59 PM

Yeah, I agree, but with the empty base class implementation it would be required.

 

If we knew more about what the OP wanted we could probably suggest a better design I feel.


"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

#6 metsfan   Members   -  Reputation: 654

Like
0Likes
Like

Posted 23 April 2013 - 10:00 AM

Some other options that I don't see being mentioned here, if in fact A and B should not inherit from the same base class:

 

  • use "void *" as the value type for your map (though this gets tricky, because you need to know the type when you pull the object and dynamic_cast it)
  • use "boost::any" as the value type for your map (this is a little better but can cause problems if you have exceptions off because boost throws an execption if boost::any_cast fails)

Edited by metsfan, 23 April 2013 - 10:01 AM.


#7 metsfan   Members   -  Reputation: 654

Like
0Likes
Like

Posted 23 April 2013 - 10:03 AM

A better design would be to use std::shared_ptr (or boost::shared_ptr if your compiler doesn't support the C++11 standard) instead of raw pointers.

 

As for this point...wouldn't you say this advice is a bit misleading?  Yes, shared pointers have their place, but shouldn't the train of thought be "by default, try to make sure a pointer is owned by only one object, and if that is impossible or breaks the design of the application, then use a shared pointer"?  



#8 ApochPiQ   Moderators   -  Reputation: 16401

Like
2Likes
Like

Posted 23 April 2013 - 10:59 AM

Then use a std::unique_ptr, problem solved.

My point wasn't to make a blanket rule for all pointer usage (so I'm not sure why you read that into my post) but rather to point the OP in the direction of smart pointers as a preference over raw pointers.

#9 Soundstone   Members   -  Reputation: 191

Like
0Likes
Like

Posted 23 April 2013 - 02:48 PM

Thanks for advice guys, Ill try and make it more clear what I am trying to accomplish.

 

In visualization terms think of a box, this box can hold exactly 9 objects in it,each of the 9 spots is a numbered index 0-8, but only objects of either type A or type B. I would like a associative container that can keep track of which everything in the box, including which type is located at which box index. I tried doing this through inheritance and downcasting but I feel like there should be a better way. I have data members in A and B that extend a baseclass. The classes look similar to this:

Class BaseClass
{
public:
	BaseClass(string, int, string, int, int, int);
	~BaseClass();
	void SetName(string value) 	{name = value;}
	void SetType(int value)		{type = value;}
	void SetFlavor(string value)	{flavor = value;}
	void SetNum1(int value)		{num1 = value;}
	void SetNum2(int value)		{num2 = value;}
	void SetNum3(int value)		{num3 = value;}
	const string GetName()		{return name;}
	const int  	GetType()	{return type;}
	const string	GetFlavor()	{return flavor;}
	Const int	GetNum1()	{return num1;}
	const int	GetNum2()	{return num2;}
	const int	GetNum3()	{return num3;}
	
protected:
	string  name; //name of object
	int	type; //flagged 0 for type a and 1 for type B
	string  flavor; //flavor text about object
	int     num1; 
	int 	num2;
	int 	num3;
}

Class A : public BaseClass
{
public:
	A(string name, int type, string flavor, int num1, int num2, int num3, int num4
              int num5, float speed)
          :BaseClass(name, type, flavor, num1, num2, num3);
	~A();
	//getters and setters for speed, num4 and num5...

private:
	float speed;
	int 	num4;
	int 	num5;
}

Class B : public BaseClass
{
public:
	B(string name, int type, string flavor, int num1, int num2, int num3 int bInt)
          :BaseClass(name, type, flavor, num1, num2, num3);
	~B();
	void SetBInt(int value)		{bInt = value;}
	const int GetBInt()		{return bInt;}
private:
	int bInt;
}

so that when in class box it would look like this:

 

//Box.h

#include "A.h"
#include "B.h"
#include "BaseClass.h"
Class Box
{
public:
	Box();
	~Box();
	void AddObjectTypeA(A* objectA);
	void AddObjectTypeB(B* objectB);
private:
	map<int, BaseClass*> myBoxMap;
}


//Box.cpp

void Box::AddObjectTypeA(A* objectA)
{
	bool full = false;
	for(int i = 0; i < myBoxMap.size(); i++)
	{
		if(i == myBoxMap.size())
		{
			//map is full - only want 9 objects inside no more
			full = true;
			break;
		}
		if(mFoundOffensiveMap[i]->GetOffCellName() != "") //preloaded during box construction with 9 objects inside of type Basecell name = ""
		{
			//bad choice cell full already
			continue;
		}
		else if(mFoundOffensiveMap[i]->GetOffCellName() == "")
		{
			//good cell choice - it's empty currently
			myBoxMap[i]->SetName(cell->GetName());
			myBoxMap[i]->SetType(cell->GetType());
			myBoxMap[i]->Setnum1(cell->Getnum1());
			myBoxMap[i]->Setnum2(cell->Getnum2());
			myBoxMap[i]->Setnum3(cell->Getnum3());
			myBoxMap[i]->Setnum4(cell->GetNum4());
			myBoxMap[i]->setSpeed(cell->GetSpeed());
			myBoxMap[i]->setnum5(cell->Getnum5());
			
			
			break;
		}
	}
	return full;

}

finally in main.cpp

 

 


 

//main.cpp

int main()
{
	A* myObjA = new A("Steve", 0, "is a man", 4, 6, 7, 17, 8, 3.4f);
	Box* myBox;

	myBox->AddObjectA(myObjA);
				 
	/* during the above call I would like to set myObjA into the Box 
	   at the first available index without another object. It tests
	   this in the function by checking if name = "". */
}

I hope that better explains it. As it currently stand I'm getting access violation errors when in the function to add. Those errors are keeping me from knowing if this set up will actually work for what I want. Thank you again, for reading into this. I truly appreciate the quick responses you have offered.

 

Regards,
Soundstone


Edited by Soundstone, 23 April 2013 - 02:53 PM.


#10 Paradigm Shifter   Crossbones+   -  Reputation: 5435

Like
0Likes
Like

Posted 23 April 2013 - 02:52 PM

The access violations would be because you use a pointer to a Box (Box* myBox) but you don't create an object of type B by doing

 

Box* myBox = new Box();

 

But you are probably better off just having a Box local variable (or member variable of a class) so you don't have to create it with operator new in the first place.


Edited by Paradigm Shifter, 23 April 2013 - 02:53 PM.

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

#11 j-jorge   Members   -  Reputation: 317

Like
0Likes
Like

Posted 23 April 2013 - 03:13 PM

For this kind of problem I use claw::multi_type_map :

For example :

typedef claw::meta::type_list_maker<A*, B*>::result my_type_list;
claw::multi_type_map<int, my_type_list> my_multi_map;

my_multi_map.set<0>( myA );
my_multi_map.set<1>( myB );

Then I use a visitor to explore the map.

With this solution, A and B do not need to share a common base class, nor to have any virtual stuff. There is also no dynamic_cast.


C++ developper with some 2D art skills
http://www.stuff-o-matic.com


#12 Trienco   Crossbones+   -  Reputation: 2224

Like
2Likes
Like

Posted 23 April 2013 - 10:34 PM

I'm confused. You say it's _exactly_ 9 slots and they are _consecutively_ numbered 0-8? At which point did that make you think "map<int, ptr>" instead of "array<ptr, 9>"?

 

Why fill it with dummy instances and loop through all the pointers and call functions on them, instead of just checking them for 0?

 

And why even do that, instead of just adding a simple "numItems" variable to box that keeps track of the next free slot?

 

 

class Box
{
public:
    Box() : numItems(0) {}
 
    void addItem(shared_ptr<Item> item)
    {
         if (numItems < 9)
         {
              items[numItems++] = item;
         }
    }
 
private:
   std::array<shared_ptr<Type>, 9>;
};

 

Why pass a shared_ptr? Because in your code you never check if the item was successfully added. You ownership is completely unknown at this point. If you delete it at the calling site and it was added -> access violation. If you don't delete it and it wasn't added -> memory leak.

 

Why a shared_ptr? Without knowing the usage of that box, it is safer to allow code that is currently using an item from the box to keep it alive, just in case it gets removed from the box in between. Using a smart pointer also means there is no need to explicitly delete an object in the box before replacing it (if that is even a use case).

 

 

Your main function (as explained above) also has a big issue.

 

 

int main()
{
     Box myBox; //This has no business being a pointer in the first place
     myBox.addItem(make_shared<ItemA>("Steve", 0, "is a man", 4, 6, 7, 17, 8, 3.4f);
}

 

 

The real problem is of course trying to stuff two completely unrelated types into the same container. Why? It will just result in a huge mess of if/else and dynamic_casts or getType(). Put them in two different arrays and let the box class hide that fact. Of course if you ever want to access an item in the box, you're back to the same problem. What type should that function return?

 

Generally you want to hide all members and use a _common_ interface for A and B. Depending on what they are supposed to do, try to abstract that. You want them to print their data? Have a virtual function "print" in both of them (and the base class). Don't even think about placing that code outside the classes and have it rip the data from their guts.


Edited by Trienco, 24 April 2013 - 09:49 AM.

f@dzhttp://festini.device-zero.de

#13 Soundstone   Members   -  Reputation: 191

Like
0Likes
Like

Posted 06 May 2013 - 08:19 AM

Hi Trienco, Thanks for the help and the advice, what you present seems to be a good solution to go off of. I think my originial intention with the map container over the array was that I wanted to have a string key that I could easily reference the items by. The items in the container are similar and inherit from an abstract base item class. The end game was to use this as part of an inventory system for a game. Not necessarily what the player is carrying but what they are storing into an external container in game. This container has actions that it takes much like an AI player. I was really tied to the idea of item lookup through string key. Thank you all for your comments and help. I'll be thinking through this much more in depth.






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS