如何创建一个操纵器来调用流中下一个对象中的特定函数?

How to create a manipulator that would call a specific function in the next object in the stream?

假设我有一个 class 如下:

class A
{
...private members
public:
void write_text(std::ostream& os); //writes members as text
void write_binary(std::ostream& os); //writes objects as binary
};

如何创建一个类似 textbinary 的操纵器,具体取决于我可以调用适当的函数 write_text()write_binary() 来写入文件流,如下所示:

std::ofstream file1("textfile.txt");
std::ofstream file2("binfile.bin");

A obj; // assume obj has data members set 
file1<<text<<obj; // here obj.write_text() should be invoked 
file2<<binary<<obj; // here obj.write_binary() should be invoked

我是否需要像这样 example 那样在流中存储状态或变量之类的东西才能做到这一点,还是有更简单的方法?

标准用于操纵输入和输出操作的主要方式有两种。

1。在流状态中存储值

您可以使用 std::ios_base::xalloc().
在流中存储格式化状态 这为您提供了每个流中的 longvoid* 值,您可以使用 iword() / pword() 访问这些值。

这与 std::hex, std::boolalpha 等标准 io 操纵器使用的机制相同。

请注意,如果您更改流状态,它将保持该状态,直到您再次更改它,例如:

std::cout << std::hex << 16; // will be outputted in hexadecimal
std::cout << 12; // will still be outputted in hexadecimal

std::cout << std::dec << 16; // will be outputted in decimal
std::cout << 12; // still decimal

你可以,例如为您的 A class:

实现它

class A {
public:
    void write_text(std::ostream& os) const {
        os << "TEXT";
    }

    void write_binary(std::ostream& os) const {
        os << "BINARY";
    }
};

// this gives us the unique index we need for pword() / iword()
inline int getAFormatIndex() {
    static int idx = std::ios_base::xalloc();
    return idx;
}

std::ostream& operator<<(std::ostream& os, A const& a) {
    std::ostream::sentry s{os};
    if(!s) return os;
    
    if(os.iword(getAFormatIndex()) == 0)
        a.write_text(os);
    else
        a.write_binary(os);

    return os;
}

struct text_t {};
struct binary_t {};

inline constexpr text_t text;
inline constexpr binary_t binary;

// change to text mode
std::ostream& operator<<(std::ostream& os, text_t const&) {
    os.iword(getAFormatIndex()) = 0;
    return os;
}

// change to binary mode
std::ostream& operator<<(std::ostream& os, binary_t const&) {
    os.iword(getAFormatIndex()) = 1;
    return os;
}
  • operator<< for A检查当前存储在流中的格式类型(0为文本,1为二进制)并调用相应的方法
  • text & binary 是 io 操纵器,当应用于流时会更改流状态。

用法示例:

A a;
std::cout << text << a;
std::cout << binary << a;
std::cout << a; // still in binary format

godbolt example

2。包装函数

您还会在标准库中遇到的另一种 io 操纵器是更改单个元素的输入/输出的包装器。

这方面的例子是 std::quoted, std::get_money, std::put_money,等等...

这些函数只改变单个操作的格式,与上面的方法相反,改变所有后续输入/输出操作的格式。 示例:

std::cout << std::put_money(12.34); // will be formatted as monetary value
std::cout << 12.34; // normal double output
std::cout << std::quoted("foo"); // -> "foo"
std::cout << "foo"; // -> foo

你可以,例如为您的 A class:

实现它

class A {
public:
    void write_text(std::ostream& os) const {
        os << "TEXT";
    }

    void write_binary(std::ostream& os) const {
        os << "BINARY";
    }
};

std::ostream& operator<<(std::ostream& os, A const& a) {
    std::ostream::sentry s{os};
    if(!s) return os;

    a.write_text(os);

    return os;
}

struct binary_impl { A const& a; };

std::ostream& operator<<(std::ostream& os, binary_impl const& b) {
    std::ostream::sentry s{os};
    if(!s) return os;

    b.a.write_binary(os);

    return os;
}

binary_impl binary(A const& a) {
    return { a };
}

// text is the default, so we need no wrapper
A const& text(A const& a) {
    return a;
}
  • 我们基本上使用了一个包装器对象 (binary_impl),它为 A 个对象实现了不同的 operator<<

用法示例:

A a;
std::cout << text(a);
std::cout << binary(a);
std::cout << a; // default is text format

godbolt example


上面列出的方法只是标准库本身使用的方法(因此可能是最受认可的方法)。

你当然也可以为它创建你自己的自定义方法,例如通过使用 return 对象的成员方法,将以特定方式序列化对象:

class A {
public:
    void write_text(std::ostream& os) const {
        os << "TEXT";
    }

    void write_binary(std::ostream& os) const {
        os << "BINARY";
    }

    struct as_text_t { A const& a; };
    struct as_binary_t { A const& a; };

    as_text_t as_text() const {
        return { *this };
    }

    as_binary_t as_binary() const {
        return { *this };
    }
};

std::ostream& operator<<(std::ostream& os, A::as_text_t const& el) {
    std::ostream::sentry s{os};
    if(!s) return os;

    el.a.write_text(os);

    return os;
}

std::ostream& operator<<(std::ostream& os, A::as_binary_t const& el) {
    std::ostream::sentry s{os};
    if(!s) return os;

    el.a.write_binary(os);

    return os;
}

用法:

A a;
std::cout << a.as_text();
std::cout << a.as_binary();

godbolt example