How to inherit from std::ostream?
Asked Answered
W

6

46

I've been googling around and I just can't find a simple answer to this. And it should be simple, as the STL generally is.

I want to define MyOStream which inherits publicly from std::ostream. Let's say I want to call foo() each time something is written into my stream.

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

I understand that the public interface of ostream is non-virtual, so how can it be done? I want clients to be able to use both operator<< and write() and put() on MyOStream and have use the extended ability of my class.

Woad answered 21/4, 2009 at 12:37 Comment(1)
The STL might be simple, but that's only one part of the C++ standard library. The iostreams library have nothing to do with (what was once) the STL. STL is basically contianers + iterators + algorithms. Iostreams, locales and all that has a completely different origin, and is generally a pain to work with ;)Decimeter
T
26

It's not a simple question, unfortunately. The classes you should derive from are the basic_ classes, such as basic_ostream. However, derivation from a stream may not be what you want, you may want to derive from a stream buffer instead, and then use this class to instantiate an existing stream class.

The whole area is complex, but there is an excellent book about it Standard C++ IOStreams and Locales, which I suggest you take a look at before going any further.

Tarrasa answered 21/4, 2009 at 12:49 Comment(2)
I was going to go look up my copy of this book, but you've saved me having to. +1Dynatron
@Tarrasa I can not find any reference to astream on the web. What is it?Hewie
F
48

I was spinning my head around how to do the same thing and i found out it's actually not that hard.

Basically just subclass the ostream and the streambuf objects, and construct the ostream with itself as the buffer. the virtual overflow() from std::streambuf will be called for every character sent to the stream. To fit your example I just made a foo() function and called it.

#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;
}

EDIT: The old code used the wrong initialization order. Although it had no visible side effects, the streambuf object should be initialized before passing it to the ostream object. Since C++ initializes parents left to right, I moved std::streambuf to the left to make the code correct.

EDIT: I changed the code to inherit std::streambuf privately to keep the interface cleaner and keep the class encapsulated.

Fluorspar answered 12/11, 2013 at 15:21 Comment(8)
Works perfectly! Should be the accepted answer, though it is an old question.National
I agree with ZXcvbnM, this should be the accepted answer. The accepted answer contains a useful reference but does not really provide a solution. Ben provides a simple working solution. +1.Fortenberry
Finally I found a solution after looking for it for so long time, this should be the accepted answer like people said.Creedon
Many thanks! After such a long time I finally found a solution in this answer. This should be the accepted one.Mn
I guess one usually does not want clients to use Bar as std::streambuf. In this case private inheritance seems like the way to go: class Bar : private std::streambuf, public std::ostream {};Nobelium
@MatthäusBrandl You're right. I updated the code. Plus i added the added methods into the private space. It makes the interface much cleaner. Thanks for the pointing that out.Fluorspar
Calling put() for every character that is put into the stream is very slow.Hilariohilarious
See my method for how to do appropriate buffering instead of put every characterKalie
T
26

It's not a simple question, unfortunately. The classes you should derive from are the basic_ classes, such as basic_ostream. However, derivation from a stream may not be what you want, you may want to derive from a stream buffer instead, and then use this class to instantiate an existing stream class.

The whole area is complex, but there is an excellent book about it Standard C++ IOStreams and Locales, which I suggest you take a look at before going any further.

Tarrasa answered 21/4, 2009 at 12:49 Comment(2)
I was going to go look up my copy of this book, but you've saved me having to. +1Dynatron
@Tarrasa I can not find any reference to astream on the web. What is it?Hewie
A
21

Another working hack to achieve a similar effect is to use template and composition

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";}
Atomicity answered 11/5, 2011 at 15:20 Comment(4)
this doesn't support stuff like hex or endlParticipation
Why not? T will get std::hex's type.Atomicity
@ElazarLeibovich: It works for std::hex but not for std::endl or std::flush (and probably some others). This is because it can not resolve T to the appropriate function type. Adding the following to LoggedStream resolves the issue: LoggedStream const& operator<<(std::ostream& (*F)(std::ostream&)) const { F(out); return *this; }Euphemie
@LokiAstari do you know how to do it and still support std::endl?Denicedenie
B
6

I don't know if this is correct solution, but I inherited from std::ostream this way. It uses a buffer inherited from std::basic_streambuf and gets 64 characters at a time (or less if flushed) and sends them to a generic putChars() method where the actual handling of data is done. It also demonstrates how to give user data.

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;
}
Battology answered 26/4, 2010 at 10:27 Comment(1)
This is flawed. You are passing basic_ostream a pointer no a not yet constructed buffer, since the base class is initialized before the data member. See Ben's answer for more on this.Nobelium
K
2

Ben's method works but it is a terrible method in the real-world application. With his method, there is no in-memory buffer at all and you are essentially outputting every single character.

To achieve the goal, you must create two types of classes.

  1. A filebuf class derived from std::streambuf with virtual methods sync(), overflow(),xsputn(),seekoff(),seekpos() overrode. It also needs to have appropriate buffering.

  2. A stream class derived from std::basic_ostream. It should have a private member as your customized filebuf, for example buf, and call std::basic_ios<CharT,Traits>::init like this->init(&buf) in its constructor. You DON'T need to define any other methods because std::basic_ostream will handle it for you. Once finishing the initiation step, this->rdbuf() will return &buf.

The underlying call stack is like the following

  • basic_ostream& operator<<:
    rdbuf()->sputn, which will call rdbuf()->xsputn

  • basic_ostream& put( char_type ch )
    rdbuf()->sputc, which will call rdbuf()->overflow

  • basic_ostream& write( const char_type* s, std::streamsize count)

    rdbuf()->sputn, which will call rdbuf()->xsputn

  • pos_type tellp()
    rdbuf()->pubseekoff, which will call rdbuf()->seekoff

  • basic_ostream& seekp( off_type off, std::ios_base::seekdir dir )
    rdbuf()->pubseekpos, which will call rdbuf()->seekpos

  • basic_ostream& flush()
    rdbuf()->pubsync, which will call rdbuf()->sync

Here is the an example, the full example is here https://github.com/luohancfd/mpistream

In most scenarios, you only need to change the functions to open_file, close_file, seek_pos, tell_pos, and write_data. By tweaking buf_size, you can get a significant performance improvement.

// GPL v3.0
class MPI_filebuf : public std::streambuf {
public:
  using Base = std::streambuf;
  using char_type = typename Base::char_type;
  using int_type = typename Base::int_type;
  using pos_type = typename Base::pos_type;
  using off_type = typename Base::off_type;

private:
  static const std::streamsize buf_size = BUFSIZ;
  char buffer_[buf_size];

  MPI_File fhw;
  bool opened;

  /**
   * @brief Always save one extra space in buffer_
   *        for overflow
   */
  inline void reset_ptr() { setp(buffer_, buffer_ + buf_size - 1); }

protected:
  /**
   * @brief For output streams, this typically results in writing the contents
   * of the put area into the associated sequence, i.e. flushing of the output
   * buffer.
   *
   * @return int Returns ​0​ on success, -1 otherwise. The base class
   * version returns ​0​.
   */
  inline int sync() override {
    int ret = 0;
    if (pbase() < pptr()) {
      const int_type tmp = overflow();
      if (traits_type::eq_int_type(tmp, traits_type::eof())) {
        ret = -1;
      }
    }
    return ret;
  }

  /**
   * @brief Write overflowed chars to file, derived from std::streambuf
   *        It's user's responsibility to maintain correct sequence of
   *        output as we are using shared file pointer
   *
   * @param ch
   * @return int_type Returns unspecified value not equal to Traits::eof() on
   * success, Traits::eof() on failure.
   */
  inline int_type overflow(int_type ch = traits_type::eof()) override {
    // https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/fstream.tcc
    int_type ret = traits_type::eof();
    const bool testeof = traits_type::eq_int_type(ch, ret);

    if (pptr() == nullptr) {
      reset_ptr();
      if (!testeof) {
        ret = sputc(ch);
      }
    } else {
      if (!testeof) {
        *pptr() = traits_type::to_char_type(ch);
        pbump(1);
      }
      if (write(pbase(), pptr() - pbase())) {
        ret = traits_type::not_eof(ch);
      }
      reset_ptr();
    }
    return ret;
  }

  /**
   * @brief Writes \c count characters to the output sequence from the character
   * array whose first element is pointed to by \c s . Overwrite this function
   * to achieve no_buffered I/O
   *
   * @param s
   * @param n
   * @return std::streamsize
   */
  inline std::streamsize xsputn(const char_type *s,
                                std::streamsize n) override {
    std::streamsize bufavail = epptr() - pptr();
    std::streamsize ret = n;

    // fill buffer up to "buf_size"
    std::streamsize nfill = std::min(n, bufavail + 1);
    std::copy(s, s + nfill, pptr());
    pbump(nfill); // if nfill == bufavail+1, pptr() == epptr()

    if (nfill == bufavail + 1) {
      // equiv: bufavail + 1<= n
      if (!write(pbase(), pptr() - pbase())) {
        ret = -1;
      } else {
        reset_ptr();
        s += nfill;
        n -= nfill;

        /*
          repeatedly write every chunk until there is
          less data than buf_size - 1
        */
        while (n >= buf_size - 1) {
          write(s, buf_size);
          s += buf_size;
          n -= buf_size;
        }
        std::copy(s, s + n, pptr());
        pbump(n);
      }
    }
    return ret;
  }

  /**
   * @brief Sets the position indicator of the input and/or output
   *        sequence relative to some other position. It will flush
   *        the internal buffer to the file
   * @note  This function is collective, which means seekp(), tellp()
   *        need to be called by all processors
   *
   * @param off relative position to set the position indicator to.
   * @param dir defines base position to apply the relative offset to.
   *            It can be one of the following constants: beg, cur, end
   * @param which
   * @return pos_type The resulting absolute position as defined by the position
   * indicator.
   */
  inline pos_type
  seekoff(off_type off, std::ios_base::seekdir dir,
          __attribute__((__unused__))
          std::ios_base::openmode which = std::ios_base::out) override {
    int ret = pos_type(off_type(-1));
    if (is_open()) {
      int whence;
      if (dir == std::ios_base::beg)
        whence = MPI_SEEK_SET;
      else if (dir == std::ios_base::cur)
        whence = MPI_SEEK_CUR;
      else
        whence = MPI_SEEK_END;

      sync(); /*!< write data to the file */
      if (off != off_type(0) || whence != SEEK_CUR) {
        if (MPI_File_seek_shared(fhw, off, whence)) {
          // fail to seek
          return ret;
        }
      }
      MPI_Offset tmp;
      MPI_File_get_position_shared(fhw, &tmp);
      ret = pos_type(tmp);
    }
    return ret;
  }

  inline pos_type seekpos(pos_type pos, __attribute__((__unused__))
                                        std::ios_base::openmode which =
                                            std::ios_base::out) override {
    return seekoff(off_type(pos), std::ios_base::beg);
  }

  /**
   * @brief Method doing the real writing. It moves the data in the
   *        internal buffer to the file
   *
   * @param pbeg
   * @param nch
   * @return true  Succeed to write
   * @return false Fail to write
   */
  inline bool write(const char_type *pbeg, std::streamsize nch) {
    return nch == 0 ||
           !MPI_File_write_shared(fhw, pbeg, nch, MPI_CHAR, MPI_STATUS_IGNORE);
  }

public:
  MPI_filebuf() : buffer_{}, opened(false) {
    setp(buffer_, buffer_ + buf_size - 1);
  }
  virtual ~MPI_filebuf() override {
    if (opened)
      close();
  };

  /**
   * @brief return nullptr if fail
   *
   * @param file_name
   * @return MPI_filebuf*
   */
  MPI_filebuf *open(const char file_name[]);
  inline bool is_open() const { return opened; }
  MPI_filebuf *close() {
    sync();
    return MPI_File_close(&fhw) ? nullptr : this;
  }


};



/* ---------------------------------------------------------------------- */

class mpistream : public std::basic_ostream<char> {
public:
  // Types
  using Base = std::basic_ostream<char>;
  using int_type = typename Base::int_type;
  using char_type = typename Base::char_type;
  using pos_type = typename Base::pos_type;
  using off_type = typename Base::off_type;
  using traits_type = typename Base::traits_type;

  // Non-standard types:
  using filebuf_type = MPI_filebuf;
  using ostream_type = Base;

private:
  filebuf_type filebuf;

public:
  mpistream() : ostream_type(), filebuf() { this->init(&filebuf); }
  mpistream(const char file_name[]) : ostream_type(), filebuf() {
    this->init(&filebuf);
    open(file_name);
  }
  mpistream(const mpistream &) = delete;
  mpistream(mpistream &&__rhs)
      : ostream_type(std::move(__rhs)), filebuf(std::move(__rhs.filebuf)) {
    ostream_type::set_rdbuf(&filebuf);
  }
  ~mpistream() {}

  inline void open(const char file_name[]) {
    if (filebuf.open(file_name) == nullptr) {
      this->setstate(std::ios_base::failbit);
    } else {
      this->clear();
    }
  }
  inline bool is_open() const { return filebuf.is_open(); }
  inline void close() {
    if (!filebuf.close()) {
      this->setstate(ios_base::failbit);
    }
  }

};

Kalie answered 1/7, 2023 at 4:45 Comment(0)
U
-2

Composition, not inheritance. Your class contains, "wraps" an ostream&, and forwards to it (after calling foo()).

Usm answered 21/4, 2009 at 12:44 Comment(3)
Please post some code that illustrates how this would work with the existing << operators. And note foo() is to be called each time such an operator is used.Tarrasa
Composition is not always the best solution, just as inheritance isn't. ostream has a dozen of overloaded operators implemented for it, you don't expect anyone to really rewrite all the public interface of ostream just to add a small functionality to a class.Woad
There's also the issue that people write their own operator<< methods that assume an ostream. If you're not actually an ostream, those won't get invoked.Elbertina

© 2022 - 2024 — McMap. All rights reserved.