• Advertisement
Sign in to follow this  

Can a program user 'name' an inherited object/instance?

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

If you intended to correct an error in the post then please contact us.

Recommended Posts

I want a program user to make a selection from a list of items and then input a simple individual ID tag ie; A1 or B3 etc at the same time or soon after.
The program then instantiates the object, based on that user's selection (from a host of inherited classes), but importantly the ID tag the user enters must be GIVEN to that instantiated object. Perhaps 'given' is the wrong term? Attached? Whatever term is appropriate, the end result must be that the instantiated instance must have that same input ID entered by the user?
Any ideas how to achieve this?

Share this post


Link to post
Share on other sites
Advertisement
I don't really understand what you're trying to achieve. Could you please provide a more concrete example that might get us a feel for how you intend to use this?

Share this post


Link to post
Share on other sites

class BaseClasssWithId
{
public:
BaseClassWithId(const char* ID)
{
memcpy(m_id, ID, sizof(ID) * numberElementsInID);
}
protected:
char* m_id;
}




This is psuedo C++ offcourse!

Share this post


Link to post
Share on other sites
Are you saying that the user must select which of several suclasses to instantiate? Depending on the language, there can be multiple ways to do this. In almost any, you can make a (potentially long) series of if-else if- etc. statements and compare the player input to instantiate the proper subclass. In some languages where classes are first-class (such as Python), you could make a map or dictionary which maps the input type (integer or string, most likely) to a class. Then you can just instantiate the mapped-to class normally.

Also, you might want to explain what the end effect you're trying to achieve is. There might be a better way than this.

Edit: Oh, is that not the part you want help with? If so, I suppose NightCreature's answer might suffice, though it looks like odd C-with-classes more than C++ (why no std::string?). But, the general idea is to give the base class an ID value which can be assigned either in the constructor or in a SetID function.

This, though raises the question of why something must know its own ID. Many times an object should not be responsible for holding its own ID (single responsibility principle and all that). If all you need is to look them up based on IDs, then you can make a map or dictionary which takes an ID as a key and a base class instance as a value.

Share this post


Link to post
Share on other sites
Lets say there is a list the user can select from. IE: A list of cars. The player in this game selects a Porsche.
In my program each variety of car is an inherited class from Car base class.
The user must then also cin input a code tag, anything, a name, a letter, etc.
The program then creates the chosen object, ie; it instantiates the Porsche.
But I want it to be named based on the cin input from the player.
The program creates the Porsche but I want that particular instance when its created to be named only as per the players input.
IE player selects Posrsche and then is requested to enter a ID and he types in A2. Thus when the porsche is instantiated; Porsche A2; The A2 is thus the name of that particular instance.
I dont know if thats clear as mud still?

Share this post


Link to post
Share on other sites
(See my edit two posts up.)

What do you want to then do with the name? That makes all the difference in the world.

Share this post


Link to post
Share on other sites
Thanks nightcreature.
Its basically just to manage my nephews little model car game. Give him some AI. And just to give me another project to learn c++.
I realised that at various times he would have to enter stuff about the cars and people in his 'town', but to do that all I could think of was giving each item an ID tag. Then when things changed in the 'lego town' he could simply enter the ID which he's given to each item (car or person etc). The program would also tell him to do stuff with some of the cars and people etc. Actually kind of fun making the AI.
I couldnt think of any other way apart from him entering a ID tag/name that may change each time he played the game.

Thanks for the code, I shall decipher it tomorrow and see if I can get it to work.

Share this post


Link to post
Share on other sites
kinda like NightCreature83 but with less to "decipher"


class carBase
{
void setId(std::string _id) { id = _id; }
std::string getId() { return id; }

protected:

std::string id;
}



ps: Ezbez's suggestion with the map is clearly a more convenient solution.

Share this post


Link to post
Share on other sites
Quote:
Original post by MikeWhiskyTango
Thanks nightcreature.
Its basically just to manage my nephews little model car game. Give him some AI. And just to give me another project to learn c++.
I realised that at various times he would have to enter stuff about the cars and people in his 'town', but to do that all I could think of was giving each item an ID tag. Then when things changed in the 'lego town' he could simply enter the ID which he's given to each item (car or person etc). The program would also tell him to do stuff with some of the cars and people etc. Actually kind of fun making the AI.
I couldnt think of any other way apart from him entering a ID tag/name that may change each time he played the game.

Thanks for the code, I shall decipher it tomorrow and see if I can get it to work.


This is how I would do this. First, download camp and have a look at the tutorials. Basically camp offers the possibility to create instances at runtime, without you knowing the concrete type. This works by first registering all possible types with camp and the doing stuff like the following:


// retrieving a class by its name
const camp::Class& metaclass = camp::classByName("MyClass");

// constructing an instance of the class
MyClass* object = metaclass.construct<MyClass>(camp::Args(10, 5.f));

// wrapping the C++ instance into a typeless object
camp::UserObject metaobject = object;

// calling an object's function by its name
metaobject.call("func");

// reading and writing an objet's property by its name
metaobject.set("prop", 50);
int x = metaobject.get("prop");




As you can see, you can easily instantiate a class without knowing it's type. The important thing is that camp::Args is a vector of variants, which means they can (nearly) take any type you want.

Now concerning your example, you might do something similar to this:

std::string carname;
std::string arg1, arg2;
std::cout &lt;&lt; "Please enter the car you want to instantiate: ";
std::cin &gt;&gt; carname;
const camp::Class& metaClass = camp::classByName(carname);
std::cout &lt;&lt; "Okay, now enter the constructor's arguments";
std::cin &gt;&gt; arg1 &gt;&gt; arg2;
CarBase* object = metaClass.construct&lt;CarBase&gt;(camp::Args(arg1, arg2));


This is typed right of my mind and only demonstrates what you can do with camp. Please not that using std::cin the way I used it is NOT the recommended way (why?), I just want to write less code.
Camp is very powerful (no I'm not advertising :D) and really suited for that kind of problem. Ideally you would ask the metaclass about the number and types of parameters the constructor requires, however this is currently not implemented in camp (but adding this manually is a matter of one hour at most).

Share this post


Link to post
Share on other sites
Thanks to all replies.
I have no idea regarding STL maps and dictionaries but shall look into them.
The 'camp' looks interesting.

Just to explain why my second post seems to ignore nightcreature's previous response. It was due to my typing and submitting a response to the second query at the same time nightcreature had already submitted his answer. Sorry for the 'crossed wires'.

Just to clarify scope's code, is there any reason to prefix 'string' with the 'std::' if I have 'using namespace std;' as a header?


Share this post


Link to post
Share on other sites
Quote:
Original post by MikeWhiskyTango
Just to clarify scope's code, is there any reason to prefix 'string' with the 'std::' if I have 'using namespace std;' as a header?


No, but you shouldn't really be 'using namespace std;', if you need to make a lot of calls to the same part of the standard library 'using std::whatever;' in a scope that will apply to your frequent calls only is my preferred practise (I'm guessing at least some others would say the same?).

Personally I like to prefix it all with std:: for clarity, the only time I tend to 'using std::' is if I'm doing a lot of input/output using the standard library, or I have a container that is quite a mouthful.

Share this post


Link to post
Share on other sites
I still have a problem in actually linking the input ID by the player to the actual derived instance. I looked at maps briefly and thought it a bit more advanced than where I am at, but if that is what I really need then...

Here is some of the code. Note that there are many more derived classes than shown and larger switch statements. Tried to boil it down a little.



#include <iostream>
#include "cstdio"
#include <cctype>
#include <ctime>
#include <string>

// People is only one of 3 base classes (others are Cars and Buildings)
class People {
public:
People():
attribute(0)
//std::string () ???
{}
int attribute;
std::string id;

void setId(std::string tag) { id = tag; }
std::string getId() { return id; }

};

//shopowners is one of many classes derived from People
class Shopowners : public People {
public:
Shopowners()
{
attribute = 2;
id;
}

};

int selection();
void datafunc();
void populate();

void move() //this interface may not seem a requirement but it is for this kids game
{
char yesno = 'y';
std::cout << "If you want to move a car or person does it have a sticker on it? y/n \n";
std::cin>>yesno;
if (yesno=='y') {datafunc();}
else selection();

}

int selection()
{
int choice;
std::cout<<" Which toy do you want to move?\n";
std::cout<< " 1. A person.\n";
std::cout<< " 2. A car.\n";
std::cin>> choice;

switch (choice){

case 1: populate();

//case 2:
//this would have several menus.

}
return 0;
}

void populate()
{
std::string name;
std::cout << "Enter a code ID tag, ie; A1 C3 etc\n";
std::cin.ignore(256, '\n');
std::getline (std::cin, name);
Shopowners fireman; fireman.setId (name);
// this may then go to events() or some other function etc

}

void events (People &personA)
{
// this contains events and tasks involving the derived classes sent here
// this will also require the id tags for buildings etc.
}

void datafunc () //this is an important interface with player
{
std::string idtag;
std::cout<< "Enter the sticker name on the toy... "; //Jack, car1 etc ;
std::cin.ignore(256, '\n');
std::getline (std::cin, idtag);


//
// its the code here that I cant work out.
// the 'idtag' entered by the player must link with 1 of a possible 100+ derived instances.
//
//events( ); //this must take the chosen instance [matching the idtag] to events()
}

int main()
{

move();
selection();
datafunc();
// other functions in gameloop
system ("pause");
return 0;
}



As you can see the main problem is in the void datafunc and linking the input ID tag with the actual object.

Share this post


Link to post
Share on other sites
Now I can see your problem. Every one of your classes probably has an id member, however this member is bound to an instance of your class, although you want it to be bound to the type of classes (ie. id should be the same for ALL instance of that particular type). One possible way to do this is to change id to be a static member:


class ShopOwners
{
public:
static std::string id() { return m_id; }
private:
static std::string m_id;
}
std::string ShopOwners::m_id = "ShopOwners";

// Inside datafunc()
boost::shared_ptr<People> people;
if(idtag == Shopowners::getId()) people = boost::make_shared<Shopowners>();
else if(...) ....
else
{
std::cout << "Unknown idtag" << std::endl;
return;
}

event(*people);



One important thing is that you cannot know the exact type you need during compile time, therefore I create a pointer to the base (using a boost::shared_ptr in this case). If you don't want to use boost::shared_ptr right now, the following would work as well (although I wouldn't recommend it):


People* people = NULL;
if(idtag == Shopowners::getId()) people = new Shopowners();
...

Share this post


Link to post
Share on other sites
Thanks for that reply Sis Shadowman.

One thing, you stated that using that last piece of code 'is not recommended'. Any particular reason? I'm assuming its not the accepted practice...

Share this post


Link to post
Share on other sites
It's because programmers easily forget to delete stuff that was allocated with new/malloc. It becomes more of a problem when you throw exceptions or when you use code that throws (or may throw):


Person* person = new Person(); //< If the constructor of Person throws, then delete is called automatically
person->foo(); //< If this function throws then your pointer to the Person instance is gone and you can never get it back again: you have a leak.




If we use a smart pointer like std::auto_ptr or boost::shared_ptr, then the following code never leaks:

std::auto_ptr<Person> person(new Person()); //< If the constructor of Person throws, then delete is called automatically
person->foo(); //< If this function throws then person is destructed during stack unwinding, since it's an object on the stack
// The destructor of std::auto_ptr takes care of deleting the stored pointer




There are many types of smart_ptrs, and I really recommend using them (when appropriate):
-boost::shared_ptr: All instances of the same boost::shared_ptr share their object, it is only deleted when all instances run out of scope
-std::auto_ptr: One instances owns the stored pointer and takes care of deleting it (there is no sharing)
-com pointers: When you start dealing with COM interfaces, you will notices that they offer AddRef() and Release() functions that you must call when appropriate, a smart com pointer does this for you
-many more

Share this post


Link to post
Share on other sites
Thanks again for that info, SiS-Shadowman.

My problem is that I have absolutely zero experience with anything such as boost or camp, nor have any knowledge in regards to STL libraries etc. Nevertheless you have shown some very interesting things that I should be pursuing.

I got the program to work after some 'unresolved external' held me up but have yet to verify whether the tagged object was actually passed to events function. I think once I mate it all back together with the main program I will be able to judge whether it has all worked.

I wont mark the topic as resolved just yet in case someone has any other ideas.
Thanks very much.

Share this post


Link to post
Share on other sites
Apparently still got problems I cant figure out.

If I have two inherited instances derived from People class, ie Shopowners and Policemen I cant seem to get the datafunc function to pass the correct one to randomEvents function. It will always skip the 'if' statment and pass the 'else' statement even when I swap the contents around. It doesnt appear to be matching the contents of idtag with the getrid(). Apart from that the program works.
It should end in randomEvents function and cout 13 or 25. (13 if you chose a policeman and 25 if you chose the shopowner.) In the current set up it will always cout 13 even if I instantiate a shopowner object.


#include <iostream>
#include "cstdio"
#include <cctype>
#include <ctime>
#include <string>

class People {
public:
People():
attribute (0)
{}
int attribute;

void setId(std::string tag) { id = tag; }
static std::string getid() { return id; }

protected:
static std::string id;
};


std::string People::id = "Shopowners";


class Shopowners : public People {

public:

Shopowners()
{
attribute = 25;
id;

}

};

class Policemen : public People {

public:

Policemen()
{
attribute = 13;
id;

}

};




void selection();
void datafunc();
void shops();
void cops();
int renadomEvents();


void move()
{
char yesno = 'y';
std::cout << "If you want to move a car or person does it have a name-sticker on it? y/n \n";
std::cin>>yesno;
if (yesno=='y') {datafunc();}
else selection();

}

void selection()
{
int choice;
std::cout<<" Which toy do you want to move?\n";
std::cout<< " 1. A shopowner.\n";
std::cout<< " 2. A policeman.\n";
std::cout<< " 3. A car.\n";
std::cin>> choice;

switch (choice){

case 1: shops();
case 2: cops();
//case 3 / 4 ,5 ...2043 etc. split up into sub menus

}

}

void shops()
{
std::string name;
std::cout << "Enter the name you want to give him.\n";
std::cin.ignore(256, '\n');
std::getline (std::cin, name);
Shopowners ownerOne; ownerOne.setId (name);
datafunc();

}

void cops()
{
std::string name;
std::cout << "Enter the name you want to give him. \n";
std::cin.ignore(256, '\n');
std::getline (std::cin, name);
Policemen copperOne; copperOne.setId (name);
datafunc();

}


void randomEvents (People &personA)
{
std::cout << personA.attribute<<"\n\n"; //currently prog should end here with print out of attribute
}

void datafunc ()
{
std::string idtag;
std::cout<< "Type in the name on the sticker. \n";

std::getline (std::cin, idtag);

People* people = NULL;
if(idtag == Shopowners::getid()) {people = new Shopowners();} //skipping this ??????????
else(idtag == Policemen::getid()); {people = new Policemen();}

randomEvents(*people);
}

int main()
{

move();
// other functions in gameloop
system ("pause");
return 0;
}
[source/]

Appreciate any help, driving me crazy for hours.Thanks.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement