c++ fstream - creating own formatting flags

跟風遠走 提交于 2020-05-29 03:22:06

问题


i need to create new flags for the format of the output file. i have a class

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

and i want to use it like:

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

this will save an xml file.

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

and this an json file.

How can i do this?


回答1:


You can easily create yor own manipulators, either hijacking an existing flag or using std::ios_base::xalloc to obtain new stream specific memory, e.g. (in the implementation file of 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;
}

Declare xml and json in your header, and the job is done.

(Having said this, I rather think that this is a bit of an abuse of manipulators. Formats like xml go beyond simple, local formatting, and are best handled by a separate class, which owns the ostream, and writes the entire stream, and not just individual objects.)




回答2:


This issue is the biggest flaw in the iostream library.

James Kanze's solution is a partial one that will work in your own classes but in general objects are given a distinct way of streaming.

My usual means is to create my own wrapper class with a function that you can pass into your stream, and with xml would contain overloads to xml_node() or xml_attribute() e.g.

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

will set the attribute Id to whatever is in the variable in xml format.

I have also written node scopes too so they will write to stream the node-opening text on construction and automatically write the closing logic on destruction.

The advantage of my method over James Kanze's solution is that it is extensible. I think James Kanze's closing comment suggests he doesn't endorse his solution and would probably use something more like mine.

With the solution above, in order to add more formats you have to edit the operator<< functions all over the place, whilst json formatting code would be a completely different set of functions and if you added yet another format you would add the code for it without having to edit any existing code.

For inputting, by the way, for XML you would use an existing DOM or SAX parser and wouldn't be using iostream directly in this way.




回答3:


The simplest way that comes to mind is to start by creating a tag type and a single instance thereof:

struct JsonStreamTag {} json;

Then let such a tag construct an object to wrap the stream:

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;

};

The constructor is protected to ensure that you can only use some_ostream << json (1) to construct a JsonStream. The other insertion operator (2) does the actual formatting. You then define an overload of write_json() (3) for every relevant type:

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.

Alternatively, omit (2) and add overloads for operator<<(JsonStream&, T) instead.

Then just follow the same process to write the corresponding XmlStream using XmlStreamTag and write_xml(). This presumes that your output can be constructed completely from the particular values you’re writing; if you need some header or footer that’s the same across every file you’ll write, just use the constructor and destructor:

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

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


来源:https://stackoverflow.com/questions/9629949/c-fstream-creating-own-formatting-flags

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!