通过从文本文件中读取对象的名称来构造对象

时间:2016-07-04 00:38:56

标签: c++ inheritance

我有一个名为shapes的基类,其派生类是3D形状,即Ball或Tetraeder。 我的程序应该从文本文件中读取形状类型及其参数,并将音量和区域写入输出文本。

#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;
}


}

我的问题现在是如何从文本文件中的字符串创建对象,尤其是在不知道所有派生类的情况下。

应该可以在不更改代码的情况下为程序添加更多形状,只需添加新文件即可。

2 个答案:

答案 0 :(得分:2)

问题是:

  

现在我的问题是如何从一个字符串中创建一个对象   textfile,尤其是在不知道所有派生类的情况下。

答案是:你必须知道所有派生类。

C ++ does not have reflection。因此,所有类名都在编译时绑定,这种工厂别无选择,只能做一些变化:

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

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

有各种不同的方法和设计模式可以自动化一些这种苦差事,并使其足够灵活,以避免必须显式维护所有子类的硬编码列表。

我将概述一个简短而简短的方法。我之前使用的一个非常简单的,并且实现了相同的结果:一个可以通过名称实例化给定子类的工厂,其方式是您不必手动编辑工厂,并添加一个几行代码。为新子类创建工厂的整个过程可以巧妙地包装到创建新子类的过程中,使其成为一个相当防弹,划分区域的解决方案。

考虑一种为这些子类注册工厂的简单机制:

typedef Shape (*shape_factory_t)();

Shape是你的超类形状。

工厂的工作方式如下:

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

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

所以现在你有了所有工厂的地图。您可以通过类名查找单个地图而不是无限if语句,并调用相应的工厂,例如:

auto iter=all_factories.find(name);

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

return (*iter->second)();

好的,那个部分得到了照顾。现在问题变成:如何为每个子类注册工厂。

假设您有Circle的实现:

class Circle : public Shape {

    class initializer;

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

};

然后,在实现此子类的circle.cpp中:

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;

通过这种方式,Circle类将自己注册到工厂,该工厂按类名创建给定Shape的实例。您可以单独继续并实现所有其他子类,而无需触及主工厂代码。您可以以相同的方式声明您的Box子类,并让它自己注册到工厂,然后工厂将自动知道创建Box类(可能是通过调用create_box()函数),名称为“box”。

还有一个需要注意的细节:初始化顺序。如您所知,不同翻译单元中全局范围对象的相对初始化顺序是实现定义的,否则C ++不会指定。

所有工厂函数的全局std::map必须在所有子类尝试注册之前构建,并在应用程序启动时将自己放入映射中。

这是一个相当典型的static initialization order fiasco问题,有几个已知的解决方案。解释in this answer的那个应该可以正常工作。

答案 1 :(得分:0)

C ++不是那么灵活。添加新形状意味着添加新类(因为您已经创建了Shapes,Ball和Tetraeder类,我假设您想要创建更多类)。如果添加新类,则必须更改代码,这意味着必须重新编译。

知道派生类是什么。你是编码它们的人,所以你也可以列出它们。关于程序灵活性,你可以做的最好的事情是使用头文件,你似乎已经在做了。

至于从文本文件中的字符串创建对象(当你知道3D对象类是什么时),你可以解析字符串,读取它想要制作的形状,然后做一些相当简单的事情。这样:

//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