【问题标题】:How to deal with iomanips in custom output operators?如何处理自定义输出运算符中的iomanips?
【发布时间】:2019-01-15 22:13:02
【问题描述】:

我刚刚遇到了将自定义输出运算符与 io-manipulators 相结合的问题。也许我的期望完全偏离了,但如果

std::cout << foo() << "\n";

打印

00

那么我会期待

std::cout << std::left << std::setw(20) << foo() << "!\n"

打印

00                   !

但是考虑到这个实现

#include <iostream>
#include <iomanip>

struct foo { int a,b; };
std::ostream& operator<<(std::ostream& out, const foo& f) {
    out << f.a << f.b;
    return out;
}

int main() {
    std::cout << foo() << "\n";
    std::cout << std::left << std::setw(20) <<  foo() << "!";
}

屏幕上打印的是什么

00
0                   0!

我基本上看到了两种选择:A) 我的期望是错误的。 B)我改用这个实现:

std::ostream& operator<<(std::ostream& out, const foo& f) {
    std::stringstream ss;
    ss << f.a << f.b;
    out << ss.str();
    return out;
}

但是,考虑到大多数时候没有使用 io 操纵器,这似乎是相当大的开销。

在自定义输出运算符中“正确”处理 io-manipulators 的惯用方式是什么?

【问题讨论】:

  • 我的解决方案是像您在B 中所做的那样对其进行字符串化。由于输出已经很慢,您甚至可能没有注意到命中。
  • @NathanOliver 嗯...我记得很久以前我想知道我是否应该养成写foo::toString() 或超载ostream::&lt;&lt; 的习惯,现在我开始认为它是最好两者兼得
  • 那将是另一种方法。然后,如果您较便宜的operator &lt;&lt; 破坏了格式,您可以选择调用更昂贵的to_string
  • @NathanOliver 我的意思是&lt;&lt; 调用toString,嗯,不知道,必须考虑一下;)
  • @user463035818 诸如set::leftstd::setw() 等在std::ostream 上设置标志的内容通常仅适用于@987654339 上的下一个 调用operator&lt;&lt;使用out &lt;&lt; f.a &lt;&lt; f.b; 时,@fa` 而不是f.b。这是记录在案的行为。如果您想以特定方式格式化多个值,您必须先自己格式化它们,然后将最终结果发送到 out,就像使用 out &lt;&lt; ss.str(); 一样

标签: c++ iomanip


【解决方案1】:

恐怕没有简单的答案。如果您只需要处理 std::setwstd::left,则您的解决方案是惯用的,但是对于其他操作,您必须决定格式化程序的行为。

想象一下,例如,如果你的结构有 floats 而不是 ints:

struct foo { float a,b; };

然后,您的用户尝试这样做:

const long double pi = std::acos(-1.L);
std::cout << std::setprecision(10) << foo{0.0f, pi} << "!\n"

这是您必须决定的时候:您是要尊重输出流的精度属性,还是要忽略它?您当前的实现会忽略它,因为它在另一个流中进行实际转换。

要尊重精度属性,您必须复制它:

std::ostream& operator<<(std::ostream& out, const foo& f) {
    std::stringstream ss;
    ss.precision(out.precision());
    ss << f.a << f.b;
    out << ss.str();
    return out;
}

对于您的整数情况,您还必须考虑是否会尊重std::setbase

同样的推理必须适用于其他操纵器,例如std::setfill

【讨论】:

  • 呃,对了,现在我记得toString 的缺点...想对我自己的答案投反对票;)。实际上我想知道是否有一种方法可以找出给定流上哪些操纵器是“活跃的”
  • @user463035818 std::ostream 有相应的方法:flags()precision()width()fill() 等。您必须根据需要单独阅读它们。
【解决方案2】:

不一定是惯用的,但一种可能的方法是:

struct foo { 
    int a,b;
    std::string toString() {
        std::stringstream ss;
        ss << a << b;
        return ss.str();
    }
};

std::ostream& operator<<(std::ostream& out, const foo& f) {
    out << a << b;
    return out;
};

现在调用者可以选择输出是否作为一个整体:

std::cout << std::left << std::setw(20) << foo().toString() << "!"; // output as expected
std::cout << foo();                                                 // output as expected 
                                                                    // and no unnecessary overhead

也有人可能会争辩说输出已经很慢了,所以一点额外的开销并没有什么坏处,只需按照 stringify 方法实现输出运算符:

std::ostream& operator<<(std::ostream& out, const foo& f) {
    out << f.toString();
    return out;
}

这也修正了第一种方法的小丑,它基本上用几乎相同的代码两次实现了相同的东西。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-05
    • 1970-01-01
    • 2019-06-11
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多