【问题标题】:Overload handling of std::endl?std::endl 的重载处理?
【发布时间】:2011-01-13 20:12:45
【问题描述】:

我想定义一个类 MyStream 以便:

MyStream myStream;
myStream << 1 << 2 << 3 << std::endl << 5 << 6 << std::endl << 7 << 8 << std::endl;

给出输出

[blah]123
[blah]56
[blah]78

基本上,我想在前面插入一个“[blah]”,然后在每个非终止std::endl?

之后插入

这里的难点不是逻辑管理,而是检测和重载std::endl的处理。有没有优雅的方法来做到这一点?

谢谢!

编辑:我不需要关于逻辑管理的建议。我需要知道如何检测/重载打印std::endl

【问题讨论】:

  • 您只需要使用具有自己唯一同步()版本的版本覆盖流缓冲区

标签: c++ overloading iostream manipulators endl


【解决方案1】:

您需要做的是编写自己的流缓冲区:当流缓冲区被刷新时,您会输出前缀字符和流的内容。

以下工作是因为std::endl 导致以下情况。

  1. '\n' 添加到流中。

  2. 在直播中调用flush()

  3. 这会在流缓冲区上调用pubsync()

    1. 这会调用虚方法sync()
    2. 重写此虚拟方法以完成您想要的工作。
#include <iostream>
#include <sstream>

class MyStream: public std::ostream
{
    // Write a stream buffer that prefixes each line with Plop
    class MyStreamBuf: public std::stringbuf
    {
        std::ostream&   output;
        public:
            MyStreamBuf(std::ostream& str)
                :output(str)
            {}
            ~MyStreamBuf() {
                if (pbase() != pptr()) {
                    putOutput();
                }
            }
   
        // When we sync the stream with the output. 
        // 1) Output Plop then the buffer
        // 2) Reset the buffer
        // 3) flush the actual output stream we are using.
        virtual int sync() {
            putOutput();
            return 0;
        }
        void putOutput() {
            // Called by destructor.
            // destructor can not call virtual methods.
            output << "[blah]" << str();
            str("");
            output.flush();
        }
    };

    // My Stream just uses a version of my special buffer
    MyStreamBuf buffer;
    public:
        MyStream(std::ostream& str)
            :std::ostream(&buffer)
            ,buffer(str)
        {
        }
};


int main()
{
    MyStream myStream(std::cout);
    myStream << 1 << 2 << 3 << std::endl << 5 << 6 << std::endl << 7 << 8 << std::endl;
}
> ./a.out
[blah]123 
[blah]56 
[blah]78
>

【讨论】:

  • -1:你抄袭了我的建议,却没有给予信任,反而留下批评(嗯?),而且只做了一半。您没有覆盖overflow,因此如果putc('\n')sync 被调用之前导致溢出,您的代码将失败。
  • 哦,stringbuf 中永远不会发生溢出。抱歉……这里是日出……困了。
  • 是的……如果您应用令牌编辑,我将撤销该投票……明天
  • @potatoswatter:认为我抄袭你有点自负。不涉及复制。你想出了对可能性的描述。我想出了一个解决方案。我们完全独立地完成了它。猜猜看:我以前做过这种事情:-)
  • 我希望通过读取 std::cin 或程序结束来触发刷新。你有什么建议吗?
【解决方案2】:

MyStream 类的重载运算符必须设置一个 previous-printed-token-was-endl 标志。

然后,如果打印下一个对象,则可以在其前面插入[blah]

std::endl 是一个获取并返回对std::ostream 的引用的函数。要检测它是否已转移到您的流中,您必须在您的类型和此类函数之间重载 operator&lt;&lt;

MyStream& operator<<( std::ostream&(*f)(std::ostream&) )
{
    std::cout << f;

    if( f == std::endl )
    {
        _lastTokenWasEndl = true;
    }

    return *this;
}

【讨论】:

  • 不幸的是,这不仅仅检测到 std::endl - 它检测到任何没有参数的操纵器。但我想在
  • 在编辑帖子的过程中,我不确定与 endl 的比较是否有效,但我对其进行了测试,它确实有效 :-)
  • 这不起作用的原因与您永远无法从 ostream 派生的原因相同:格式化插入器被定义为返回 ostream&amp;。即使您覆盖所有默认值,仍然有用户定义的ostream &amp;operator&lt;&lt;( ostream &amp;, my_type const &amp; ) 浮动。然后my_stream &lt;&lt; my_type(5) &lt;&lt; endl; 调用operator&lt;&lt;( ostream&amp;, manipulator ),而不是operator&lt;&lt;( MyStream&amp;, manipulator )
  • 这应该可以工作,除了 GCC 抱怨“假设强制转换为类型 'std::basic_ostream >& (*)(std::basic_ostream >&)' 来自重载函数”,在 fstd::endl 的比较中。 @Neil:这不会“关闭”任何操纵器;它们被传递给std::cout 并在那里应用。
  • @Gabriel,你应该按照 GCC 的建议去做:cast if (f == (std::basic_ostream&lt;char&gt;&amp; (*)(std::basic_ostream&lt;char&gt;&amp;)) &amp;std::endl) { ... }
【解决方案3】:

原则上同意尼尔。

您想要更改缓冲区的行为,因为这是扩展 iostream 的唯一方法。 endl 这样做:

flush(__os.put(__os.widen('\n')));

widen 返回一个字符,所以你不能把你的字符串放在那里。 put 调用putc,它不是一个虚函数,只是偶尔会挂接到overflow。您可以在flush 拦截,它调用缓冲区的sync。您需要拦截并更改所有换行符,因为它们是 overflowed 或手动 synced 并将它们转换为您的字符串。

设计一个覆盖缓冲区类很麻烦,因为basic_streambuf 期望直接访问它的缓冲区内存。这可以防止您轻松地将 I/O 请求传递给预先存在的basic_streambuf。您需要冒险并假设您知道流缓冲区类,并从中派生。 (据我所知,cincout 不能保证使用 basic_filebuf。)然后,只需添加 virtual overflowsync。 (请参阅 §27.5.2.4.5/3 和 27.5.2.4.2/7。)执行替换可能需要额外的空间,因此请小心提前分配。

- 或 -

只需在您自己的命名空间中声明一个新的endl,或者更好的是,一个根本不称为endl 的操纵器!

【讨论】:

  • 你把事情复杂化了。使用 std::stringbuf 类进行缓冲。
  • @Martin:他问如何改变ostream 的行为。我只能假设他想处理文件。无论如何,即使您使用stringbuf,您仍然处于派生、重载和替换的相同情况:它不会简化任何事情。另一方面,忘记整个流的混乱并对mystringstream.str()的结果执行搜索和替换是非常合理的!
  • @Patatoswatter:请阅读我的解决方案。我认为 30 行(包括 cmets)相对微不足道。
  • @Martin:是的,这很简单,但就复杂性而言,它介于我的想法和“使用 std::stringbuf 类”之间。
【解决方案4】:

与其尝试修改std::endl 的行为,不如创建一个过滤流缓冲区来完成这项工作。 James Kanze 有一个 example 显示如何在每个输出行的开头插入时间戳。只需稍作修改即可将其更改为每行所需的任何前缀。

【讨论】:

    【解决方案5】:

    我使用函数指针。对于不习惯 C 的人来说,这听起来很可怕,但在大多数情况下它的效率要高得多。这是一个例子:

    #include <iostream>
    
    class Foo
    {
    public:
        Foo& operator<<(const char* str) { std::cout << str; return *this; }
        // If your compiler allows it, you can omit the "fun" from *fun below.  It'll make it an anonymous parameter, though...
        Foo& operator<<(std::ostream& (*fun)(std::ostream&)) { std::cout << std::endl; }
    } foo;
    
    int main(int argc,char **argv)
    {
        foo << "This is a test!" << std::endl;
        return 0;
    }
    

    如果你真的想要,你可以检查 endl 的地址,以确认你没有得到一些 OTHER void/void 函数,但我认为在大多数情况下不值得。我希望这会有所帮助。

    【讨论】:

      【解决方案6】:

      我有同样的问题,我认为 Potatoswatter 的第二个答案有优点:“只需在您自己的命名空间中声明一个新的 endl,或者更好的是,一个根本不称为 endl 的操纵器!”

      所以我发现了如何编写一个完全不难的自定义操纵器:

      #include <sstream>
      #include <iostream>
      
      class log_t : public std::ostringstream
      {
          public:
      };
      
      
      std::ostream& custom_endl(std::ostream& out)
      {
          log_t *log = dynamic_cast<log_t*>(&out);
          if (log)
          {
              std::cout << "custom endl succeeded.\n";
          }
          out << std::endl;
          return out;
      }
      
      std::ostream& custom_flush(std::ostream& out)
      {
          log_t *log = dynamic_cast<log_t*>(&out);
          if (log)
          {
              std::cout << "custom flush succeeded.\n";
          }
          out << std::flush;
          return out;
      }
      
      int main(int argc, char **argv)
      {
          log_t log;
      
          log << "custom endl test" << custom_endl;
          log << "custom flush test" << custom_flush;
      
          std::cout << "Contents of log:\n" << log.str() << std::endl;
      }
      

      这是输出:

      custom endl succeeded.
      custom flush succeeded.
      Contents of log:
      custom endl test
      custom flush test
      

      在这里,我创建了两个自定义操纵器,一个处理endl,一个处理flush。您可以向这两个函数添加您想要的任何处理,因为您有一个指向 log_t 对象的指针。

      【讨论】:

        【解决方案7】:

        您无法更改std::endl - 正如它的名字所暗示的那样,它是 C++ 标准库的一部分并且它的行为是固定的。当它收到 end of line 时,您需要更改流本身的行为。就个人而言,我不认为这值得付出努力,但如果您想涉足这个领域,我强烈建议您阅读本书Standard C++ IOStreams & Locales

        【讨论】:

        • 好书。但我不推荐初学者。但是,如果您想学习如何操作流类,则必须阅读。
        • @Martin 我也不会向初学者推荐它,但是编写专门的流不是初学者的任务。
        • 同意: :-)
        猜你喜欢
        • 2020-03-19
        • 2010-11-11
        • 2018-03-02
        • 2017-10-14
        • 1970-01-01
        • 2015-04-19
        • 2018-11-15
        相关资源
        最近更新 更多