2

I have a base class named shapes with derived class of a 3D shape, i.e. Ball or Tetraeder. My program should read the type of the shape and it's parameters from a text file and write the volume and area to an output text.

#include <fstream>
#include <string>
#include <sstream>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include "Shape.h"
#include "Ball.h"
#include <vector>

using namespace std;

int
main( int argc, char** argv )
{
string input = string(argv[1]);
string output = string(argv[2]);

ifstream file(input);
string line;

string shapename;
int nx = atoi(argv[3]);
int ny = atoi(argv[4]);
int nz = atoi(argv[5]);
while (std::getline(file, line))
{
    std::stringstream lineStream(line);

    lineStream >> shapename;
    int value;
    std::vector<int> lineData;
    while (lineStream >> value)
    {
        lineData.push_back(value);
    }

    Shape * objShape = new shapename(lineData);
    objShape -> calc_volume;
    objShape -> calc_projection(nx,ny,nz);

    std::ofstream f(output);
    f << objShape -> get_volume() << " " << objShape -> get_projection << endl;
}


}

My Question is now how can i create an object from a string in a textfile, especially without knowing all derived classes.

It should be possible to add more shapes to the program without changing the code, only by adding new files.

Tholtus
  • 25
  • 4

2 Answers2

2

The question is:

My Question is now how can i create an object from a string in a textfile, especially without knowing all derived classes.

The answer is: you have to know all the derived classes.

C++ does not have reflection. As such all class names are bound at compile time, and this kind of a factory has no choice but to do some variation of:

if (name == "box")
    return new Box();
else if (name == "circle")
    return new Circle();

// ... etc ... etc ...

There are various different approaches and design patterns that make it possible to automate some of this drudge work, and make this flexible enough to avoid having to explicitly maintain a hardcoded list of all subclasses.

I'll just outline a brief, very brief approach. A very simple one that I've used before, and achieves, pretty much, the same result: a factory that can instantiate a given subclass by name, in a manner where you don't have to manually edit the factory, and add a few more lines of code. The entire process of creating a factory for a new subclass can be neatly wrapped into the process of creating a new subclass, making this a fairly bulletproof, compartmentalized solution.

Consider a simple mechanism for registering a factory for these subclasses:

typedef Shape (*shape_factory_t)();

Shape is your superclass of shapes.

The factory would work something like this:

std::map<std::string, shape_factory_t> all_factories;

void register_factory(const std::string &name, shape_factory_t factory)
{
    all_factories[name]=factory;
}

So now you have a map of all your factories. Instead of an endless if statement you have a single map, which you can look up by class name, and call the appropriate factory, something like:

auto iter=all_factories.find(name);

if (iter == all_factories.end())
    throw; // Some exception, unknown subclass

return (*iter->second)();

All right, that part's taken care of. The issue now becomes: how to register a factory for each subclass.

Let's say you have an implementation of Circle:

class Circle : public Shape {

    class initializer;

// ... other things that make up the Circle

};

Then, in circle.cpp, which implements this subclass:

static Shape *create_circle()
{
    return new Circle(); // Add constructor parameters, as appropriate
}

class Circle::initializer {

public:
    initializer() {
        register_factory("circle", create_circle);
    }
};

static initializer initialize_me;

In this manner, the Circle class registers itself with the factory that creates an instance of a given Shape, by class name. You can proceed and implement all other subclasses, individually, without touching the main factory code. You can declare your Box subclass in the same manner, and have it register itself with the factory, which will then automatically know to create a Box class (presumably by invoking the create_box() function), given the name "box".

There is one other detail that needs to be taken care of: initialization order. As you know, the relative initialization order of globally-scoped objects in different translation units is implementation defined, and is otherwise unspecified by C++.

The global std::map of all factory functions must be constructed before all the subclasses try to register themselves, and put themselves into the map, when the application starts.

This is a fairly typical static initialization order fiasco question, for which there are several known solutions. The one explained in this answer should work fine, here.

Community
  • 1
  • 1
Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
0

C++ isn't so flexible. Adding new shapes would mean adding new classes (since you have already made a Shapes, a Ball and a Tetraeder class, I'm assuming you want to make more classes). And if you add new classes, you'll have to change the code, which means you have to recompile.

You have to know what the derived classes are. You're the one who codes them, so you might as well also have a list of them. The best thing you can do about your program being flexible is using header files, which you already seem to be doing anyway.

As for creating an object from a string in a text file (while you know what the 3D object classes are), you can parse the string, read what kind of a shape it wants to make and then do something fairly simple such as this:

//shapeType - a string containing the type of the 3D object

Shape *newShape;

switch(shapeType) {
case "ball":
    newShape = new Ball(...); // ... - parameters for the ball dimensions
    break;
case "tetraeder":
    newShape = new Tetraeder(...); // ... - parameters again
    break;
default:
    return -1;
}

//and now you can use newShape as you wish
Andros Rex
  • 372
  • 1
  • 7