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

Construct an object by reading it's name from text file

本文关键字:对象 取对象 文本 文件 读取      更新时间:2023-10-16

我有一个名为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;
}

}

我的问题是我现在如何从一个文本文件中的字符串创建一个对象,特别是不知道所有的派生类。

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

问题是:

我的问题是我现在如何创建一个对象从字符串在

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

c++没有反射。因此,所有的类名在编译时都是绑定的,这种工厂别无选择,只能做以下的一些变化:

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必须在所有子类尝试注册它们自己之前构造,并在应用程序启动时将它们自己放入映射中。

这是一个相当典型的静态初始化顺序失败问题,有几种已知的解决方案。

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