哪些iomanip操纵符是“粘性”的?

155

最近我遇到了一个问题,创建stringstream时犯了错,因为我错误地假设std::setw()会影响每个插入操作的stringstream,直到我明确更改它。然而,在插入后它总是未设置。

// With timestruct with value of 'Oct 7 9:04 AM'
std::stringstream ss;
ss.fill('0'); ss.setf(ios::right, ios::adjustfield);
ss << setw(2) << timestruct.tm_mday;
ss << timestruct.tm_hour;
ss << timestruct.tm_min;
std::string filingTime = ss.str(); // BAD: '0794'

所以,我有几个问题:

  • setw() 为什么是这样的?
  • 还有其他的操作符也是这样的吗?
  • std::ios_base::width()std::setw() 的行为有什么区别吗?
  • 最后,是否有在线参考资料清晰地记录了这种行为?我的供应商文档(MS Visual Studio 2005)似乎没有清楚地显示这一点。

一个解决方法在这里:http://stackoverflow.com/a/37495361/984471 - Manohar Reddy Poreddy
3个回答

102

以下是评论中的重要说明:

由Martin提供:

@Chareles: 因此,按照这个要求,所有操作符都是粘性的。除了setw,在使用后似乎会被重置。

由Charles提供:

确切地说!而且,setw表现出不同行为的唯一原因是格式化输出操作对明确的.output流进行.width(0)。

以下是导致上述结论的讨论:


查看代码,以下操作符返回对象而不是流:

setiosflags
resetiosflags
setbase
setfill
setprecision
setw

这是一种常见技术,仅对应用于流的下一个对象应用操作。不幸的是,这并不能防止它们变得粘性。测试表明,除了setw之外,它们都是粘性的。

setiosflags:  Sticky
resetiosflags:Sticky
setbase:      Sticky
setfill:      Sticky
setprecision: Sticky

所有其他操作符都返回一个流对象。因此,它们改变的任何状态信息必须记录在流对象中,并因此是永久性的(直到另一个操作符改变状态)。因此,以下操作符必须是Sticky 操作符。

[no]boolalpha
[no]showbase
[no]showpoint
[no]showpos
[no]skipws
[no]unitbuf
[no]uppercase

dec/ hex/ oct

fixed/ scientific

internal/ left/ right

这些操作实际上是对流本身执行的,而不是流对象(尽管从技术上讲,流是流对象状态的一部分)。但我认为它们不会影响流对象状态的其他部分。
ws/ endl/ ends/ flush

结论是,在我的版本中,setw似乎是唯一不粘性的操纵器。
对于Charles来说,影响链中仅下一个项目的简单技巧是:
以下是一个示例,展示了如何使用对象临时更改状态,然后通过使用对象将其恢复:
#include <iostream>
#include <iomanip>

// Private object constructed by the format object PutSquareBracket
struct SquareBracktAroundNextItem
{
    SquareBracktAroundNextItem(std::ostream& str)
        :m_str(str)
    {}
    std::ostream& m_str;
};

// New Format Object
struct PutSquareBracket
{};

// Format object passed to stream.
// All it does is return an object that can maintain state away from the
// stream object (so that it is not STICKY)
SquareBracktAroundNextItem operator<<(std::ostream& str,PutSquareBracket const& data)
{
    return SquareBracktAroundNextItem(str);
}

// The Non Sticky formatting.
// Here we temporariy set formating to fixed with a precision of 10.
// After the next value is printed we return the stream to the original state
// Then return the stream for normal processing.
template<typename T>
std::ostream& operator<<(SquareBracktAroundNextItem const& bracket,T const& data)
{
    std::ios_base::fmtflags flags               = bracket.m_str.flags();
    std::streamsize         currentPrecision    = bracket.m_str.precision();

    bracket.m_str << '[' << std::fixed << std::setprecision(10) << data << std::setprecision(currentPrecision) << ']';

    bracket.m_str.flags(flags);

    return bracket.m_str;
}


int main()
{

    std::cout << 5.34 << "\n"                        // Before 
              << PutSquareBracket() << 5.34 << "\n"  // Temp change settings.
              << 5.34 << "\n";                       // After
}


> ./a.out 
5.34
[5.3400000000]
5.34

1
然而,我可以验证setfill()实际上是“粘性的”,尽管它返回一个对象。因此,我认为这个答案是不正确的。 - John K
这段代码展示了setprecision是“粘性”的,即所有格式化输出函数都直接或间接地调用.width(0),这使得setw看起来不是“粘性的”。int main() { std::ostringstream a; std::cout << a.precision() << ' ' << a.width() << '\n'; a << std::setprecision(7) << std::setw(7); std::cout << a.precision() << ' ' << a.width() << '\n'; a << 5.6; std::cout << a.precision() << ' ' << a.width() << '\n'; return 0; } - CB Bailey
2
返回流的对象__必须__是粘性的,而返回对象的对象可能是粘性的,但不是必需的。我将使用John的信息更新答案。 - Martin York
1
我不确定我理解你的推理。所有带参数的操作符都被实现为返回未指定对象的自由函数,当该对象插入到流中时,它会对流进行操作,因为这是保留带参数插入语法的唯一(?)方式。无论哪种形式,适当的manipulator的operator<<都确保流的状态以某种方式发生了改变。两种形式都没有设置任何类型的状态哨兵。只有下一个格式化插入操作的行为决定了如果有的话重置状态的哪个部分。 - CB Bailey
3
没错!而 setw 看起来行为不同的唯一原因是格式化输出操作要求显式地 .width(0) 输出流。 - CB Bailey
显示剩余8条评论

33

“宽度(width)”似乎不起作用的原因在于,某些操作保证会在输出流上调用 .width(0) 方法。这些操作包括:

21.3.7.9 [lib.string.io]:

template<class charT, class traits, class Allocator>
  basic_ostream<charT, traits>&
    operator<<(basic_ostream<charT, traits>& os,
               const basic_string<charT,traits,Allocator>& str);

22.2.2.2.2 [lib.facet.num.put.virtuals]:所有num_put模板的do_put重载。这些被用于接受一个内置数值类型的operator<<重载和一个basic_ostream

22.2.6.2.2 [lib.locale.money.put.virtuals]:所有money_put模板的do_put重载。

27.6.2.5.4 [lib.ostream.inserters.character]:接受一个basic_ostream和该basic_ostream实例化时的char类型之一、char、signed charunsigned char,或指向这些char类型数组的指针的operator<<重载。

说实话,我不确定为什么要这样做,但是格式化输出函数不应重置ostream的其他状态。当然,如果输出操作失败,则可能设置badbitfailbit>,但这是可以预期的。

唯一我想到需要重置宽度的原因是,当尝试输出某些分隔字段时,您的分隔符被填充了会让人感到惊讶。

例如:

std::cout << std::setw(6) << 4.5 << '|' << 3.6 << '\n';

"   4.5     |   3.6      \n"
要“纠正”这个问题需要:
std::cout << std::setw(6) << 4.5 << std::setw(0) << '|' << std::setw(6) << 3.6 << std::setw(0) << '\n';

如果将宽度重置为适当的值,则可以使用以下更短的代码生成所需的输出:

std::cout << std::setw(6) << 4.5 << '|' << std::setw(6) << 3.6 << '\n';

我也在想这个理由。就我个人而言,我宁愿多打几个字,也不希望出现行为上的变化或意外情况。 - studgeek

7
setw()只影响下一个插入。这就是setw()的行为方式。setw()的行为与ios_base::width()相同。我从cplusplus.com获取了有关setw()的信息。
您可以在这里找到完整的操作符列表。从该链接中,所有流标志应保持设置,直到被另一个操作符更改。关于leftrightinternal操作符的一个注意事项:它们像其他标志一样持续存在,直到被更改。但是,它们只在流的宽度设置时才起作用,并且每行必须设置宽度。例如:
cout.width(6);
cout << right << "a" << endl;
cout.width(6);
cout << "b" << endl;
cout.width(6);
cout << "c" << endl;

会给你

>     a
>     b
>     c

但是
cout.width(6);
cout << right << "a" << endl;
cout << "b" << endl;
cout << "c" << endl;

会给你

>     a
>b
>c

输入和输出操作符不是粘性的,它们只在使用它们的地方出现一次。参数化操作符各不相同,以下是每个操作符的简要描述: setiosflags 允许您手动设置标志,可以在这里找到标志列表,因此它是粘性的。 resetiosflags 的行为类似于 setiosflags,但它取消设置指定的标志。 setbase 设置插入到流中的整数的基数(因此在基数16中为17的值为"11",在基数2中为"10001")。

setfill 设置在使用 setw 时要插入的填充字符。

setprecision 设置在插入浮点值时要使用的小数精度。

setw 通过使用 setfill 中指定的字符进行填充,仅使下一个插入达到指定的宽度。


大多数都只是设置标志,所以它们是“粘性的”。setw()似乎是唯一影响单个插入的函数。您可以在http://www.cplusplus.com/reference/iostream/manipulators/上了解更多每个函数的具体信息。 - David Brown
好的,std::hex也不是粘性的,显然,std::flushstd::setiosflags也不是粘性的。所以我认为这并不简单。 - sbi
只是测试十六进制和setiosflags(),它们似乎都是粘性的(它们只是设置标志,该流会一直保留这些标志,直到您更改它们)。 - David Brown
是的,声称 std::hex 不会粘性的网页是错误的 - 我也刚发现这一点。然而,流标志即使您不再插入 std::setiosflags 也可能会更改,因此可以将其视为非粘性。另外,std::ws 也不是粘性的。所以这并不是那么容易。 - sbi
你已经付出了相当多的努力来改善你的回答。+1 - sbi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接