tags:

views:

139

answers:

5

Hello, everyone! I am new for C++ and now work on a C++ project.The whole structure has been completed.But I have been wondering about

how to build the vector of different objects and how to read the file since the beginning.

In my problem, first I have to read a txt file with readObstacles(std::istream &fs) that has the following format:

RECTANGLE 1 2.5 2 2 0.2
CIRCLE 1 2.5 1.2
RECTANGLE 4 2 2 2 0.3

(each obstacle begins a new line)

I need to read the data information of different obstacles and store these obstacles in a vector.
Class Obstacle is the base class with two sub-classes CIRCLE and RECTANGLE.
I try to put the these different obstacles (that i think should have their data info) in a obstacle vector and then call the virtual function they both have.

Below is the code I try to do with:

vector<Obstacle> obsdata; 

Myworld::readObstacles(std::istream &fs)
{
    std::string shape;
    double num1,num2,num3,num4,num5;
    while(fs>>shape>>num1>>num2>>num3>>num4>>num5)
    {
        if(shape=="CIRCLE") {
            CIRCLE c;
            c.m_Xc=num1;
            c.m_Yc=num2;
            c.m_Radius=num3;       
            obsdata.push_back(c);
        }

        if(shape=="RECTANGLE") {
            RECTANGLE r;
            r.center_x=num1;
            r.center_y=num2;    
            r.width=num3;
            r.height=num4;
            r.angle=num5;
            obsdata.push_back(r);
        }
    }
}

MyWorld::writeMatlabDisplayCode(std::ostream &fs)
{
    for( i = 0; i < obsdata.size(); i++ )
        obsdata[i].writeMatlabDisplayCode(fs);
}

I know it doesn't work but i don't know what i should do.

Any words will help. Thanks !

+2  A: 

You have a problem called slicing: you lose the data from the derived classes if you copy to an vector containing base class instances.

You can use a vector of pointers to the base though:

std::vector<Obstacle*> obsdata;
RECTANGLE r = new RECTANGLE;
// set properties
obsdata.push_back(r);
// use it later, e.g.:
obsdata[0]->writeMatlabDisplayCode(fs);
// clean up when you don't need obstacles anymore:
for(std::vector<Obstacle*>::iterator it = obsdata.begin(); it != obsdata.end(); ++it)
    delete *it;

You also should avoid trying to read in 5 numbers every time. Instead you could read in the obstacle description and then read as many numbers as the specific obstacle needs.

Georg Fritzsche
should it be obsdata.push_back(new r) or obsdata.push_back(new RECTANGLE)And how to use them when call the function?
Lisa
Updated the answer.
Georg Fritzsche
Be careful with ownership ambiguity when storing pointers to heap objects in a container. Who owns them, and who's going to delete them?
joshperry
Thank you for your update. I understand now since i am so afraid of using pointers...
Lisa
where the delete part should be put ?in the end of writeMatlabDisplayCode function?
Lisa
Consider voting up answers then (not necessarily mine).
Georg Fritzsche
You can delete any time after you don't need your data anymore. When exactly depends on the context, though in the excerpt it would probably be at the end of or after your matlab display function.
Georg Fritzsche
+1  A: 

You can do it if you have a vector of pointers instead:

vector<Obstacle *> obsdata;

and then you "new" your subsequent CIRCLE and RECTANGLE:

    if(shape=="CIRCLE") {
        CIRCLE *c = new CIRCLE;
        c->m_Xc=num1;
        c->m_Yc=num2;
        c->m_Radius=num3;       
        obsdata.push_back(c);
    }

etc..

Jim Buck
Lisa
It should be: obsdata[i]->writeMatlabDisplayCode(fs). Without knowing your file format, it's hard to address your second concern.
Jim Buck
compared with others' code, Your CIRCLE *c = new CIRCLE; c->m_Xc=num1; seems to be the right way. are they doing it uncorrectly?
Lisa
And after reading one line, will pointer fs change to the next line automatically?
Lisa
A: 

The condition of your while loop is a problem. Circle has 2 fewer entries than rectangle. I suggest you read the shape, then depending on type read the rest of the data. You know you can read 3 data points for circle and 5 for rectangle.

Some pseudo code

while( fs >> shape )
{
  if( shape == "CIRCLE" )
  { 
        CIRCLE c;
        fs >> c.m_Xc;
        fs >> c.m_Yc;
        fs >> c.m_Radius;       
        obsdata.push_back(c);
   }

  if( shape == "RECTANGLE" )
  {
      RECTANGLE r;
      fs >> r.center_x;
      fs >> r.center_y;    
      fs >> r.width;
      fs >> r.height;
      fs >> r.angle;
      obsdata.push_back(r);
  }
}

As gf suggested in the comment, a good C++ book or tutorial on the standard library would be helpful.

nathan
i have considered that problem. I don't know how to read the file data again after i read the shape info I only know the way of fs>>shape>>num1>>num2>>num3>>num4>>num5
Lisa
if(shape=="CIRCLE") fs>>......I am so sorry that i don't know how to continute...thank you so much
Lisa
`fs >> num1 >> num2 >> num3;` - then continue setting up the circle.
Georg Fritzsche
fs>>num1>>num2>>num3 can this jump the shape part;I don't know how fs works. if you read one informatino it moves to the next?
Lisa
Yes, on reading successfully. You should probably read some book on C++ and the standard library.
Georg Fritzsche
i really wish to learn C++ systemly but the lectures are so brief and the project is tough
Lisa
+1  A: 

You could try just reading the whole line at a time then tokenize the values...

vector<Obstacle*> obsdata;

string line;
while(getline(fs, line)) {
 char *token = strtok(line.c_str(), " ");
 string shape(token);

 vector<double> numbers;
 stringstream ss;
 while(token = strtok(NULL, " ")) {
  double d;
  ss << token;
  ss >> d;

  numbers.push_back(d);
 }

 if(shape == "CIRCLE") {
  CIRCLE *c = new CIRCLE();
  c->m_Xc=numbers[0];
  c->m_Yc=numbers[1];
  c->m_Radius=numbers[2];       
  obsdata.push_back(c);
 } else if(shape == "RECTANGLE") {
  RECTANGLE *r = new RECTANGLE();
  r->center_x=numbers[0];
  r->center_y=numbers[1];    
  r->width=numbers[2];
  r->height=numbers[3];
  r->angle=numbers[4];
  obsdata.push_back(r);
 }
}

I would delete this vector of pointers in your MyWorld destructor:

~MyWorld() {
    // loop thanks to gf
    for(std::vector<Obstacle*>::iterator it = obsdata.begin(); it != obsdata.end(); ++it)
        delete *it;  
}
joshperry
Thank you for your complete answer. it helps me a lot
Lisa
upvotes always appreciated for helpful answers ;)
joshperry
where the pointer container delete part should be put ? in the end of writeMatlabDisplayCode function?
Lisa
answered that in the answer...
joshperry
Ug. Thats ugly. Change the vector to a boost::ptr_vector. It will do pointer management for you.
Martin York
I don't disagree, I probably would have done `vector<shared_ptr<Obstacle> >` but I wanted to keep it simple for the OP.
joshperry
I am sorry that should c.m_Xc be changed to c->m_Xc above?
Lisa
what does Martin York mean ? will that cause some error?
Lisa
@Lisa - sorry yes you are right about that, I've fixed it in the sample.
joshperry
I have one more question for you.If i use the way of reading data one by one,and after reading one line, will pointer fs change to the next line automatically? or should i do something ?
Lisa
@Lisa - yes this will read one line at a time, incrementing the file stream position as it goes.
joshperry
+1  A: 

All of the advice here is good but I'd like to suggest two improvements:

  • Managed your pointers (I'd pick shared_ptr but ptr_vector would also work)
  • Use an object factory

This will make your solution more robust (and not leak memory) and more scalable (you remove the 'God' function that would become a compiler bottleneck as the number of Obstacles increases).

Your code may look something like:

vector<shared_ptr<Obstacle> > obsdata; 

Myworld::readObstacles(std::istream &fs)
{
    std::string shape;
    while(fs >> shape)
    {
        try
        {
            obsdata.push_back(shared_ptr<Obstacle>(m_ObstacleCreator.Create(shape, fs));
        }
        catch(ObstacleNotKnownException& e)
        {
            // Error handling here
        }
    }
}

The only thing left is to populate m_ObstacleCreator with the knowledge of how to create the various objects based on the shape name and the istream. An example:

Obstacle * CreateCircleObstacle(istream &fs)
{
    CIRCLE *c = new CIRCLE();
    fs >> c->m_Xc >> c->m_Yc >> c->m_Radius;
}

m_ObstacleCreator.Register("CIRCLE", &CreateCircleObstacle);

You may also want to make m_ObstacleCreator a singleton (Object Factories are one of the few genuine uses for singletons IMO).

For an excellent description on the details of Object Factory implementations pick up a copy of Alexandrescu's Modern C++ Design and check out chapter eight.

MattyT