以不同格式输出对象数据的流操作

Stream Manipulation for outputting object data in different formats

假设我有一个具有以下数据成员的员工对象:

class Employee {

private:
    int _id;
    std::string _name;
    std::string _address;
    std::string _city;
    std::string _state;
    std::string _country;
    std::string _phone;
    double _salary;
...
}

我想用两种不同的方式输出:

XML

<Employee>
     <id>12345</id>
     <name>Jack Dough</name>
     <address>24437 Princeton</address>
     <city>Dearborn</city>
     <state>Michigan</state>
     <country>USA</country>
     <phone>303-427-0153</phone>
     <salary>140000</salary>
</Employee>

和 JSON-like:

id: 12345
name: Jack Dough
address: 24437 Princeton
city: Dearborn
state: Michigan
country: USA
phone: 303-427-0153
salary: 140000

我如何使用流操纵器做到这一点? 例如:

Employee* employee = new Employee(12345, "Jack Dough", "24437 Princeton", "Dearborn", "Michigan", "USA", "303-427-0153", 140000.00);
cout << toXML << employee;
cout << toJSON << employee;

首先,除非你真的需要将其实现为一个单独的操纵器,否则请考虑其他路线。两种明显的可能性是自定义语言环境,或者只是一个执行格式化和 returns 结果作为字符串的函数。前者看起来像:

std::locale myLoc(std::locale(), XML_formatter);
cout.imbue(myLoc);

cout << employee;

这使得格式样式在一般情况下对于流来说是持久的。如果你真的需要在同一个流中混合不同的样式,函数版本就简单很多:

std::string toXML(Employee const &e) { 
    std::stringstream ret;

    ret << "Employee>\n<id>" << id << "</id>"
        << // ...
    return ret.str();
}

// ...
cout << toXML(employees[i]);

如果您真的别无选择,只能将其实现为单独的操纵器,则需要存储一个标志以指示流中的当前格式。 Streams 以 xallociwordpword 的形式提供内存管理接口。基本上,xalloc给你分配一个字。 iword 允许您访问该单词作为对 long 的引用,而 pword 允许您访问它作为对 void 指针的引用。在你的情况下,你显然只需要一个或两个位,所以你可能想使用 iword 并定义几个位来指定类型的格式。您的 toXMLtoJSON 操纵器将设置适当的位,您的 operator<< 将读取它们以控制其行为。它笨拙且丑陋,但如果你愿意付出一点努力,它确实有用。

How would I be able to do so with stream manipulators?

我会告诉你怎么做,但请记住,这不是去这里的方法。专用(成员)函数或一些花哨的 OOP 模式是更好的方法。

也就是说,您可以将任意数据附加到流对象。为此,您首先需要一个 "id" 用于该数据。您可以使用 std::ios_base::xalloc:

static int formatId = ios_base::xalloc();

使用返回的数字,您可以通过 std::ios_base::iword 返回的引用获得(写入)对 long 的访问权限。 (还有std::ios_base::pword得到一个void *。)

然后,流操纵器就是可以用流(引用)调用的东西,返回另一个流引用:

ostream & toFoo(ostream & stream) {
  stream.iword(formatId) = 1;
  return stream;
}
ostream & toBar(ostream & stream) {
  stream.iword(formatId) = 2;
  return stream;
}

(注意:邪恶的魔法数字,换成更好的设计!)

这里我只是设置了一个"flag",这样最后在输出函数(操作符)中,我可以检查最后使用了哪个机械臂(如果有的话):

struct FooBar {};

ostream & operator<<(ostream & stream, FooBar const &) {
  switch (stream.iword(formatId)) {
    case 1: stream << "foo"; break;
    case 2: stream << "bar"; break;
    default: stream << "wild foobar";
  }
  return stream;
}

嗯,就是这样。我测试了:

int main() {
  FooBar f;
  cout << f << toFoo << " " << f << endl;
  cout << f << toBar << " " << f << endl;
  cout << f << endl;
  return 0;
}

(Live here)