【问题标题】:How to inherit from std::ostream?如何从 std::ostream 继承?
【发布时间】:2009-04-21 12:37:10
【问题描述】:

我一直在谷歌搜索,但我找不到一个简单的答案。它应该很简单,就像 STL 通常一样。

我想定义从 std::ostream 公开继承的 MyOStream。假设我想在每次向我的流中写入内容时调用 foo()。

class MyOStream : public ostream {
public:
  ...
private:
   void foo() { ... }
}

我知道ostream的公共接口是非虚拟的,那怎么办呢? 我希望客户能够在 MyOStream 上同时使用 operator

【问题讨论】:

  • STL 可能很简单,但这只是 C++ 标准库的一部分。 iostreams 库与(曾经的)STL 无关。 STL 基本上是 contianers + 迭代器 + 算法。 Iostreams,语言环境和所有具有完全不同来源的东西,通常很难使用;)

标签: inheritance stl ostream


【解决方案1】:

我一直在思考如何做同样的事情,我发现它实际上并不难。 基本上只是子类化 ostream 和 streambuf 对象,并以自身作为缓冲区构造 ostream。对于发送到流的每个字符,都会调用 std::streambuf 的虚拟溢出()。为了适合您的示例,我只是创建了一个 foo() 函数并调用了它。

#include <iostream>

struct Bar :  private std::streambuf , public std::ostream
{
    Bar() : std::ostream(this) {}

private:
    int overflow(int c) override
    {
        foo(c);
        return 0;
    }


    void foo(char c)
    {
        std::cout.put(c);

    }
};

int main()
{
    Bar b;
    b<<"Look a number: "<<std::hex<<29<<std::endl;

    return 0;
}

编辑:旧代码使用了错误的初始化顺序。尽管它没有可见的副作用,但应该在将 streambuf 对象传递给 ostream 对象之前对其进行初始化。由于 C++ 从左到右初始化父母,我将 std::streambuf 移到左边以使代码正确。

编辑:我将代码更改为私有继承 std::streambuf 以保持接口清洁并保持类被封装。

【讨论】:

  • 完美运行!应该是公认的答案,尽管这是一个老问题。
  • 我同意 ZXcvbnM,这应该是公认的答案。接受的答案包含有用的参考,但并没有真正提供解决方案。 Ben 提供了一个简单的工作解决方案。 +1。
  • 找了这么久终于找到了解决办法,这应该是大家说的公认的答案了。
  • 非常感谢!经过这么长时间,我终于在这个答案中找到了解决方案。这应该是公认的。
  • 我猜人们通常不希望客户使用Bar 作为std::streambuf。在这种情况下,私有继承似乎是要走的路:class Bar : private std::streambuf, public std::ostream {};
【解决方案2】:

不幸的是,这不是一个简单的问题。您应该派生的类是basic_ 类,例如basic_ostream。但是,从流派生可能不是您想要的,您可能希望从流缓冲区派生,然后使用此类实例化现有的流类。

整个领域都很复杂,但是有一本关于它的好书Standard C++ IOStreams and Locales,我建议你在继续之前先看看。

【讨论】:

  • 我本来打算去看看我这本书的副本,但你让我不得不去查。 +1
  • @anon 我在网上找不到任何对 astream 的引用。这是什么?
【解决方案3】:

实现类似效果的另一个有效技巧是使用模板和合成

class LoggedStream {
public:
  LoggedStream(ostream& _out):out(_out){}
  template<typename T>
  const LoggedStream& operator<<(const T& v) const {log();out << v;return *this;}
protected:
  virtual void log() = 0;
  ostream& out;
};

class Logger : LoggedStream {
  void log() { std::cerr << "Printing" << std::endl;}
};

int main(int,char**) {LoggedStream(std::cout) << "log" << "Three" << "times";}

【讨论】:

  • 这不支持 hex 或 endl 之类的东西
  • 为什么不呢? T 将得到 std::hex 的类型。
  • @ElazarLeibovich:它适用于std::hex,但不适用于std::endl 或std::flush(可能还有其他一些)。这是因为它无法将 T 解析为适当的函数类型。将以下内容添加到 LoggedStream 可解决问题:LoggedStream const&amp; operator&lt;&lt;(std::ostream&amp; (*F)(std::ostream&amp;)) const { F(out); return *this; }
  • @LokiAstari 你知道怎么做并且仍然支持std::endl吗?
【解决方案4】:

我不知道这是否是正确的解决方案,但我以这种方式从 std::ostream 继承。它使用从 std::basic_streambuf 继承的缓冲区,一次获取 64 个字符(如果刷新则更少),并将它们发送到一个通用的 putChars() 方法,在该方法中完成数据的实际处理。它还演示了如何提供用户数据。

Live Example

#include <streambuf>
#include <ostream>
#include <iostream>

//#define DEBUG

class MyData
{
    //example data class, not used
};

class MyBuffer : public std::basic_streambuf<char, std::char_traits<char> >
{

public:

    inline MyBuffer(MyData data) :
    data(data)
    {
        setp(buf, buf + BUF_SIZE);
    }

protected:

    // This is called when buffer becomes full. If
    // buffer is not used, then this is called every
    // time when characters are put to stream.
    inline virtual int overflow(int c = Traits::eof())
    {
#ifdef DEBUG
        std::cout << "(over)";
#endif
        // Handle output
        putChars(pbase(), pptr());
        if (c != Traits::eof()) {
            char c2 = c;
            // Handle the one character that didn't fit to buffer
            putChars(&c2, &c2 + 1);
        }
        // This tells that buffer is empty again
        setp(buf, buf + BUF_SIZE);

        return c;
    }

    // This function is called when stream is flushed,
    // for example when std::endl is put to stream.
    inline virtual int sync(void)
    {
        // Handle output
        putChars(pbase(), pptr());
        // This tells that buffer is empty again
        setp(buf, buf + BUF_SIZE);
        return 0;
    }

private:

    // For EOF detection
    typedef std::char_traits<char> Traits;

    // Work in buffer mode. It is also possible to work without buffer.
    static const size_t BUF_SIZE = 64;
    char buf[BUF_SIZE];

    // This is the example userdata
    MyData data;

    // In this function, the characters are parsed.
    inline void putChars(const char* begin, const char* end){
#ifdef DEBUG
        std::cout << "(putChars(" << static_cast<const void*>(begin) <<
            "," << static_cast<const void*>(end) << "))";
#endif
        //just print to stdout for now
        for (const char* c = begin; c < end; c++){
            std::cout << *c;
        }
    }

};

class MyOStream : public std::basic_ostream< char, std::char_traits< char > >
{

public:

    inline MyOStream(MyData data) :
    std::basic_ostream< char, std::char_traits< char > >(&buf),
    buf(data)
    {
    }

private:

    MyBuffer buf;

};

int main(void)
{
    MyData data;
    MyOStream o(data);

    for (int i = 0; i < 8; i++)
        o << "hello world! ";

    o << std::endl;

    return 0;
}

【讨论】:

  • 这是有缺陷的。您正在传递basic_ostream 一个指针而不是尚未构造的缓冲区,因为基类在数据成员之前初始化。有关更多信息,请参阅Ben's answer。
【解决方案5】:

组合,而不是继承。您的类包含“包装”一个 ostream&,并转发给它(在调用 foo() 之后)。

【讨论】:

  • 请发布一些代码来说明这将如何与现有的
  • 组合并不总是最好的解决方案,正如继承不是。 ostream 为其实现了十几个重载运算符,您不要指望任何人真正重写 ostream 的所有公共接口,只是为了向一个类添加一个小功能。
  • 还有一个问题是人们编写自己的 operator
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多