【问题标题】:Construct an object by reading it's name from text file通过从文本文件中读取对象的名称来构造对象
【发布时间】:2016-07-04 00:38:55
【问题描述】:

我有一个名为形状的基类,它具有 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++ inheritance


【解决方案1】:

问题是:

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

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

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() 函数),给定名称“盒子”。

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

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

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

【讨论】:

    【解决方案2】:

    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
    

    【讨论】:

      猜你喜欢
      • 2012-09-02
      • 1970-01-01
      • 2021-02-21
      • 2014-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-23
      • 2011-09-26
      相关资源
      最近更新 更多