c++ fstream-создание собственных флагов форматирования

мне нужно создать новые флаги для формата выходного файла. у меня есть класс

class foo{
    bar* members;
    ofstream& operator<<(ofstream&);
    ifstream& operator>>(ifstream&);
};

и я хочу использовать его как:

fstream os('filename.xml');
foo f;
os << xml << f;
os.close();

это спасет xml.

fstream os('filename.json');
foo f;
os << json << f;
os.close();

и это в JSON.

как я могу это сделать?

3 ответов


вы можете легко создать yor собственных манипуляторов, либо угон существующих флаг или использование std::ios_base::xalloc получить конкретный новый поток память, например (в файле реализации Foo:

static int const manipFlagId = std::ios_base::xalloc();

enum
{
    fmt_xml,        //  Becomes the default.
    fmt_json
};

std::ostream&
xml( std::ostream& stream )
{
    stream.iword( manipFlagId ) = fmt_xml;
    return stream;
}

std::ostream&
json( std::ostream& stream )
{
    stream.iword( manipFlagId ) = fmt_json;
    return stream;
}

std::ostream&
operator<<( std::ostream& dest, Foo const& obj )
{
    switch ( dest.iword( manipFlagId ) ) {
    case fmt_xml:
        // ...
        break;
    case fmt_json:
        //  ...
        break;
    default:
        assert(0);  //  Or log error, or abort, or...
    }
    return dest;
}

объявить xml и json в вашем заголовке, и работа сделана.

(сказав это, я скорее думаю, что это немного злоупотребление манипуляторы. Такие форматы, как xml, выходят за рамки простого, локального форматирования и лучше всего обрабатываются отдельным классом, которому принадлежит ostream, и пишет весь поток, а не только отдельные объекты.)


эта проблема является самым большим недостатком в библиотеке iostream.

решение Джеймса Канзе является частичным, которое будет работать в ваших собственных классах, но в целом объектам предоставляется отдельный способ потоковой передачи.

мое обычное средство-создать свой собственный класс-оболочку с функцией, которую вы можете передать в свой поток, и с xml будет содержать перегрузки до xml_node() или xml_attribute() например

os << xml_attribute( "Id", id );

установит идентификатор атрибута на то, что находится в переменной в xml формат.

Я также написал области узлов, поэтому они будут записывать поток текста открытия узла при построении и автоматически записывать логику закрытия при разрушении.

преимущество моего метода над решением Джеймса Канзе заключается в том, что он расширяем. Я думаю, что заключительный комментарий Джеймса Канзе предполагает, что он не поддерживает свое решение и, вероятно, использовал бы что-то вроде моего.

С вышеуказанным решением, для того, чтобы добавить больше форматов, вы должны отредактируйте оператор

для ввода, кстати, для XML вы бы использовали существующий парсер DOM или SAX и не использовали бы iostream непосредственно таким образом.


самый простой способ, который приходит на ум, - начать с создания типа тега и одного его экземпляра:

struct JsonStreamTag {} json;

тогда пусть такой тег построит объект для обертывания потока:

class JsonStream {
public:

    // (1)
    friend JsonStream operator<<(std::ostream& ostream, const JsonStreamTag&) {
        return JsonStream(ostream);
    }

    // (2)
    template<class T>
    friend JsonStream& operator<<(JsonStream& json_stream, const T& value) {
        write_json(json_stream.ostream, value); // (3)
        return json_stream;
    }

protected:

    JsonStream(std::ostream& ostream) : ostream(ostream) {}

private:

    std::ostream& ostream;

};

конструктор protected чтобы убедиться, что вы можете только использовать some_ostream << json (1) построить JsonStream. Другой оператор вставки (2) выполняет фактическое форматирование. Затем вы определяете перегрузку write_json() (3) для каждого соответствующего тип:

void write_json(std::ostream& stream, int value) {
    stream << value;
}

void write_json(std::ostream& stream, std::string value) {
    stream << '"' << escape_json(value) << '"';
}

// Overloads for double, std::vector, std::map, &c.

в качестве альтернативы опустите (2) и добавьте перегрузки для .

затем просто выполните тот же процесс, чтобы написать соответствующий XmlStream используя XmlStreamTag и write_xml(). Это предполагает, что ваш вывод может быть полностью построен из конкретных значений, которые вы пишете; если вам нужен какой-то верхний или нижний колонтитул, который одинаков для каждого файла, который вы напишете, просто используйте конструктор и деструктор:

XmlStream(std::ostream& ostream) : ostream(ostream) {
    ostream << "<?xml version=\"1.0\"?><my_document>"
}

~XmlStream() {
    ostream << "</my_document>";
}