2

我正在实现一个生成结果并将它们写入某种格式的文件的东西。

相当简单,但我希望这是动态的。

我会放弃几节课。

Data - 所有结果
的基类DataFile - 所有文件格式类型的基类,有方法addData(Data * data)

ErrorData - 派生自 Data,包含有关错误的数据。
InfoData - 派生自 Data,包含通用信息。

XmlFile - 派生自 DataFile,包含 XML 格式的数据。
BinaryFile - 派生自 DataFile,包含二进制格式的数据。

我的问题是这样的:

我在哪里实现如何将ErrorData写入XmlFile

我不喜欢在答案中看到:

  1. DataDataFileErrorDataXmlFile的新成员函数(因为这意味着我每次添加新的DataDataFile派生类时都需要添加它们)
  2. 类型转换为派生类型。
  3. 普遍的丑陋:)

我知道我的基本 C++ 虚拟化和东西,不需要特别具体。

不过,我确实很欣赏一些代码片段,它们会减少歧义。

我对制作DataWriter基类并从中派生类有一些想法,这些类将知道如何将特定类型的数据写入特定类型的DataFile,但我有点不确定具体的细节。

编辑:

我将以示例的形式进行更多说明。

让我们有 2 种新的文件格式,FormatATxtFileFormatBTxtFile

假设我们有一个InfoData对象并且它有参数:

消息行号:34
消息内容:Hello World

写入FormatATxtFile的对象在文件中如下所示:

行:34;文本:Hello World;类型:信息

FormatBTxtFile中,它看起来像这样:

@Info,34,Hello World

一种将数据导出为不同格式的方法。我不需要导入,至少现在是这样。

使用它的代码会是什么样子:

DataFile * file = DataFileFactory::createFile(type);

std::vector<Data*> data = generateData();

file->setData(data);
file->writeTo("./FileName"); // correct end is added by DataFile type, ie .txt

编辑:

似乎我没有足够清楚地说明 Xml 和二进制文件格式会出现什么问题。对不起。

让我们使用与上面相同的InfoData对象并将其推送到 XmlFile 格式。在某个元素下,它可能会产生类似这样的东西:

<InfoLog>
    <Info Line="34">Hello World</Info>
</InfoLog>

假设ErrorData类将具有以下参数:

错误的行号:56
错误文本:链接:致命错误 LNK1168
错误消息之前的 10 行:text1...
错误消息之后的 10 行:text2...
发生的整个日志:text3...

现在,当它被推入 XML 格式时,它需要完全不同。

<Problems>
    <Error>
        <TextBefore>text1...</TextBefore>
        <Text line = 56>
            LINK : fatal error LNK1168
        </Text>
        <TextAfter>text1...</TextAfter>
    </Error>
    ...
</Problems>

整个文件可能看起来像这样:

<Operation>
    <InfoLog>
        <Info Line="34">Hello World</Info>
        <Info Line="96">Goodbye cruel World</Info>
    </InfoLog>
    <Problems>
        <Error>
            <TextBefore>text1...</TextBefore>
            <Text line = 56>
                LINK : fatal error LNK1168
            </Text>
            <TextAfter>text1...</TextAfter>
        </Error>
        <Error>
            <TextBefore>sometext</TextBefore>
            <Text line = 59>
                Out of cheese error
            </Text>
            <TextAfter>moretext</TextAfter>
        </Error>
    </Problems>
</Operation>
4

4 回答 4

2

与其试图找一个地方把它放在一个类中,不如一个新函数呢?

void copyData(const ErrorData *data, DataFile *output)
{
    // ...
}

然后,您可以为要转换的任何数据类型重载此函数。

或者,您也许可以使用模板:

template<typename A, typename B> copyData(const A *data, const B *output);

然后,您可以将模板专门用于您需要支持的特定类型。

于 2011-05-06T06:43:24.383 回答
2

像标准库那样做 - 使用virtual函数/运算符。我们都可以使用istream&并从中提取我们想要的内容operator>>,而我们完全不关心底层流,无论是它cin还是fstream一个stringstream. 而是将您data的参考作为参考(Data& data)。

于 2011-05-06T06:45:44.687 回答
1

如果您考虑下面的代码,它会提供一个最小说明,说明如何将通用字段访问与通用字段流任意组合 - 考虑您的各种要求。如果适用性或实用性不明确,请告诉我....

#include <iostream>
#include <string>

struct X
{
    int i;
    double d;

    template <typename Visitor>
    void visit(Visitor& visitor)
    {
        visitor(i, "i");
        visitor(d, "d");
    }
};

struct XML
{
    XML(std::ostream& os) : os_(os) { }

    template <typename T>
    void operator()(const T& x, const char name[]) const
    {
        os_ << '<' << name << '>' << x << "</" << name << ">\n";
    }

    std::ostream& os_;
};

struct Delimiter
{
    Delimiter(std::ostream& os,
              const std::string& kvs = "=", const std::string& fs = "|")
      : os_(os), kvs_(kvs), fs_(fs)
    { }

    template <typename T>
    void operator()(const T& x, const char name[]) const
    {
        os_ << name << kvs_ << x << fs_;
    }

    std::ostream& os_;
    std::string kvs_, fs_;
};

int main()
{
    X x;
    x.i = 42;
    x.d = 3.14;

    XML xml(std::cout);
    Delimiter delimiter(std::cout);

    x.visit(xml);
    x.visit(delimiter);
}
于 2011-05-06T08:05:08.927 回答
1

我一直在玩你的问题,这就是我想出的:

#include <iostream>
#include <list>
#include <map>
#include <string>

using namespace std;

class DataFile;

class Data {
public:
    virtual void serializeTo(DataFile*) = 0;
};

class DataFile {
public:
    void addData(Data* d) {
        _data.push_back(d);
    }
    virtual void accept(string paramName, string paramValue) {
        _map[paramName] = paramValue;
    }

    virtual void writeTo(string const& filename) = 0;

protected:
    list<Data*> _data;
    map<string, string> _map;
};

class FormatATxtFile: public DataFile {
public:
    void writeTo(string const& filename) {
        cout << "writing to " << filename << ".txt:" << endl;
        for(list<Data*>::iterator it = _data.begin(); it != _data.end(); ++it) {
            (*it)->serializeTo(this);       

            cout << "Line:" << _map["Line"] << "; "
                << "Txt:" << _map["Txt"] << "; "
                << "Type: " << _map["Type"]
                << endl;
        }
        cout << endl;
    }
};

class FormatBTxtFile: public DataFile {
public:
    void writeTo(string const& filename) {
        cout << "writing to " << filename << ".b-txt" << endl;
        for(list<Data*>::iterator it = _data.begin(); it != _data.end(); ++it) {
            (*it)->serializeTo(this);

            cout << "@" << _map["Type"] << "," << _map["Line"] << "," << _map["Txt"]
                << endl;
        }
        cout << endl;
    }
};

class InfoData: public Data {
    public:
        void serializeTo(DataFile* storage) {
            storage->accept("Line", line);
            storage->accept("Txt", txt);
            storage->accept("Type", "Info");
        }
        string line;
        string txt;
    };

int main()
{
    FormatATxtFile fileA;
    FormatBTxtFile fileB;
    InfoData info34;
    info34.line = "34";
    info34.txt = "Hello World";
    InfoData info39;
    info39.line = "39";
    info39.txt = "Goodbye cruel World";
    fileA.addData(&info34);
    fileA.addData(&info39);
    fileB.addData(&info34);
    fileB.addData(&info39);
    fileA.writeTo("./Filename");
    fileB.writeTo("./Filename");    
}

实际上,它并没有真正写入文件,但很容易根据您的需要进行更改。

此示例代码的输出是:

写入 ./Filename.txt: Line:34;
文本:你好世界;类型:信息行:39;
txt:再见残酷的世界;类型:信息

写信给 ./Filename.b-txt
@Info,34,Hello World
@Info,39,再见残酷的世界

如您所见,Data需要提供DataFile由名称和值标识的参数,并且每个特化DataFile都会按照自己喜欢的方式处理它。

高温高压

于 2011-05-06T08:58:45.633 回答