如何使我的函数对重载的 iostream 提取运算符具有粘性

How can I make my function sticky for overloaded iostream extraction operators

我正在做一个学校项目,我需要经常更改其中的文本颜色。 项目目标是控制台应用程序,目前仅适用于 Windows。使用 Codeblocks 和 MinGW 进行调试。 我不是菜鸟,而是中级。

所以在代码中到处使用它是丑陋的:

SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), __col._colour_code);

即使我将它包装在一个函数中,它仍然很麻烦而且丑陋,因为你无法继续你的 cout 链。您已经打破链条,因为您必须在新语句中调用 SetColour,例如:

SetColour(GRAY);   cout << setcol(PURPLE) << " ID:[";
SetColour(AQUA);   cout << song.GetID();
SetColour(GRAY);   cout << "]" << " ";
SetColour(GREEN);  cout << song.GetTitle();
SetColour(WHITE);  cout << " by ";
SetColour(BRIGHT); cout << song.GetArtist() << "\n";

我想要的是 setwsetprecision 等功能。所以我打开 iomainp.h 并寻找一些提示:

struct _Setw { int _M_n; };

inline _Setw 
setw(int __n)
{ return { __n }; }

template<typename _CharT, typename _Traits>
inline basic_istream<_CharT, _Traits>& 
operator>>(basic_istream<_CharT, _Traits>& __is, _Setw __f)
{
  __is.width(__f._M_n);
  return __is; 
}

template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>& 
operator<<(basic_ostream<_CharT, _Traits>& __os, _Setw __f)
{
  __os.width(__f._M_n);
  return __os; 
}

所以我以 100% 类似的方式创建了自己的新函数:

enum Colour { BLACK=0x00, DARK_GREEN=0x02, WHITE=0x07, GRAY,
              BLUE=0x09, GREEN, AQUA, RED, PURPLE, YELLOW, BRIGHT };

struct _colour_struct
{
    uint8_t _colour_code;
};

inline _colour_struct setcolour (Colour colour_foregrnd, Colour colour_backgrnd =BLACK)
{
    uint8_t colour_code = colour_backgrnd;
    colour_code <<= 4;
    colour_code |= colour_foregrnd;
    return { colour_code };
}

namespace std
{
    template<typename _CharT, typename _Traits>
    inline basic_ostream<_CharT, _Traits>&
    operator<<(basic_ostream<_CharT, _Traits>& __os, _colour_struct __col)
    {
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), __col._colour_code);
        return __os;
    }
}

惊喜! (对我来说)它的工作!例如:

cout << setcolour(GRAY) << " ID:[" << setcolour(AQUA) << song.GetID() << setcolour(GRAY) << "]" << " "
     << setcolour(GREEN) << song.GetTitle()
     << setcolour(WHITE) << " by "<< setcolour(BRIGHT) << song.GetArtist() << "\n";

但是请考虑这段代码的输出:

std::cout << std::setw(20) << setcolour(AQUA) << "1st time" << "\n";
std::cout << "2nd time" << "\n";
std::cout << "3rd time" << "\n";

注意 setw 没有坚持,它在第二行被重置了。如何 ?? (调试显示没有执行额外的调用来重置它。)

但是我的 setcolour DID 坚持了程序的其余部分。为什么 ?? (尽管它 100% 类似于 setw)。

我怎样才能让 setcoloursetw 一样??? 我需要这个功能来让我的程序更干净和更有逻辑性。

我还发现了这个: Which iomanip manipulators are sticky

但是那里的答案和评论只会让我感到困惑。显然,setw调用了cout.width(0),但调试显示没有这样的调用,在iomanip.h中也没有找到这样的一行代码。也不明白那里的答案。请解释。

编辑

也许我没有直接问这个问题。 就像每次调用 cout.width(0)(在 setw 的上下文中)一样,

如何让我的 SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0)(在 setcolour 的上下文中)每次都被调用??? 我该如何解决这个问题??

Notice that setw DIDN'T stick, it was Reset in the second line. How ??

当您使用 cout << setcolour(AQUA) 时,您正在对稍后使用的程序状态进行持久更改。

std::setw() 并非如此。 std::setw() 仅设置下一个输出的宽度,然后将宽度重置为零。有关详细信息,请参阅 http://en.cppreference.com/w/cpp/io/manip/setw。特别是 Notes:

The width property of the stream will be reset to zero (meaning "unspecified") if any of the following functions are called:

  • Input
    • operator>>(basic_istream&, basic_string&)
    • operator>>(basic_ostream&, char*)
  • Output
    • Overloads 1-7 of basic_ostream::operator<<() (at Stage 3 of num_put::put())
    • operator<<(basic_ostream&, char) and operator<<(basic_ostream&, char*)
    • operator<<(basic_ostream&, basic_string&)
    • std::put_money (inside money_put::put())
    • std::quoted (when used with an output stream)

宽度有特殊处理:所有内置算子输出一个对象后都会重新设置宽度。您在代码或调试器中没有看到对 width(0) 的相应调用这一事实并不意味着它不存在!例如,它可能被内联并且没有遇到断点。关于您需要查看的代码,例如,在 std::num_put<...> 的实现中:实际输出运算符不包含代码,但 do_put() 函数包含代码。

要重置其他格式化标志,您需要挂接到每个输出操作后调用的某些操作。不过机会不多:流不支持在每个对象之后进行通用定制。以下是 可以 完成的事情,但我建议保留格式化标志。将宽度设为特殊可以说是错误的:

  • 数字格式是通过 std::num_put<cT> 中的 do_put() 虚函数完成的。这些函数可以被覆盖,例如,通过委托给基础 class 实现来进行正常格式化,然后是其他东西。

    在您的设置中,此方法的关键问题是它不适用于非数字输出。例如,输出字符串不会重置任何内容。

  • 可以设置格式化标志 std::unitbuf 在每个 [正确编写的] 输出运算符后导致刷新。刷新被转换为对 pubsync() 的调用,并最终在流 std::basic_streambuf<cT> 上调用 sync()sync() 函数可以被覆盖。也就是说,该方法将在设置将输出发送到原始流的 sone 标志时安装自定义流缓冲区和标志 std::ios_base::unitbuf,并在调用 sync() 时重置标志。

    除了有点做作之外,它还有一个问题,就是您无法区分真正的冲洗和自动冲洗(srd::ios_base::unitbuf 的主要目的是让 std::cerr 冲洗)。此外,重置发生在第一个 std::ostream::sentry 被销毁时。对于最有可能在格式化第一部分之后的复合值。

  • 无论如何,颜色格式化程序使用临时对象。该对象的构造函数可用于重置一些格式化标志。输出运算符在获得 "formatted" 时会在相应的对象上设置必要的流信息(可能使用 mutable 成员)。当然,这意味着设置格式和输出需要在同一条语句中完成。此外,同一语句的所有输出都采用相同的格式。

我不知道在某些输出后处理格式化的任何其他方法。 None 的方法效果特别好。我宁愿使用类似警卫的方法来处理 set/unset 标志,而不是试图变得聪明。