15

I would like to write a logging library of my own that provides abstraction for wherever the log entries are sent to.

The IO library of C++ already provides that kind of abstraction with std::stringstream and std::fstream. I would also like to be able to read/write from/to a socket.

I read that the proper way of extending the standard library is to inherit from std::basic_streambuf. What I don't understand is, if inheriting from std::basic_streambuf like std::basic_filebuf does, where is the need for the std::ifsream, std::ofstream and std::fstream classes ? Can't I just replace the buffer of some stream with a instance of a subclass of std::basic_streambuf which outputs where I want it to ?

So far I have done the following, but I really am not sure about what I'm doing. Is the following design correct ?

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_sock_streambuf : public std::basic_streambuf< char_type, traits_type >
{
public:

    basic_sock_streambuf()
    {

    }

    ~basic_sock_streambuf()
    {

    }

    int overflow (int c = EOF)
    {
        fputc( c, stdout ); // Temporary.
        return traits_type::to_int_type( c );
    }

    int underflow()
    {
        return fgetc( stdout ); // Temporary.
    }

    int sync()
    {
        return 0;
    }
};

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_isockstream : public std::basic_istream< char_type, traits_type >
{
};

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_osockstream : public std::basic_ostream< char_type, traits_type >
{
};

template< typename char_type, typename traits_type = std::char_traits< char_type > >
class basic_socktream : public basic_isockstream< char_type, traits_type >, public basic_osockstream< char_type, traits_type >
{
private:

    typedef basic_isockstream< char_type, traits_type > iparent;

    typedef basic_osockstream< char_type, traits_type > oparent;

    basic_sock_streambuf< char_type, traits_type > sock_sb;

    std::basic_streambuf< char_type, traits_type > * old_isb;

    std::basic_streambuf< char_type, traits_type > * old_osb;

public:

    basic_socktream()
    {
        old_isb = iparent::rdbuf( & sock_sb );
        old_osb = oparent::rdbuf( & sock_sb );
    }

    ~basic_socktream() throw()
    {
        iparent::rdbuf( old_isb );
        oparent::rdbuf( old_osb );
    }
};

EDIT : Code updated based on answers :

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_sockbuf :
    public std::basic_streambuf< char_type, traits_type >
{
public:

    basic_sockbuf()
    {
    }

    ~basic_sockbuf()
    {
    }

    int overflow( int c = EOF )
    {
        fputc( c, stdout ); // Temporary.
        return traits_type::to_int_type( c );
    }

    int underflow()
    {
        return fgetc( stdout ); // Temporary.
    }

    int sync()
    {
        return 0;
    }
};

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_isockstream :
    public std::basic_istream< char_type, traits_type >
{
private:

    typedef std::basic_istream< char_type, traits_type > parent;

    basic_sockbuf< char_type, traits_type > buffer;

public:

    basic_isockstream() :
        std::basic_istream< char_type, traits_type >::basic_istream(),
        buffer()
    {
        init( & buffer );
    }
};

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_osockstream :
    public std::basic_ostream< char_type, traits_type >
{
private:

    basic_sockbuf< char_type, traits_type > buffer;

public:

    basic_osockstream() :
        std::basic_ostream< char_type, traits_type >::basic_istream(),
        buffer()
    {
        init( & buffer );
    }
};

template<
    typename char_type,
    typename traits_type = std::char_traits< char_type > >
class basic_socktream :
    public std::basic_iostream< char_type, traits_type >,
    public basic_sockbuf< char_type, traits_type >
{
private:

    basic_sockbuf< char_type, traits_type > buffer;

public:

    basic_socktream() :
        std::basic_iostream< char_type, traits_type >::basic_iostream(),
        buffer()
    {
        std::basic_iostream< char_type, traits_type >::init( & buffer );
    }
};
Virus721
  • 8,061
  • 12
  • 67
  • 123

1 Answers1

9

std::istream and std::ostream provide formatted input and output operations. That is, converting the stream to/from numbers, strings, etc...

std::basic_streambuf is a lower-level interface that reads or writes chunks of characters to or from ...somewhere. That's what you need to subclass and implement.

And, you're on the right track. Both std::istream and std::ostream have an overloaded constructor that take a pointer to a stream buffer. So, your plan of action is:

  1. Subclass and implement your custom std::basic_streambuf.

  2. Construct a std::istream or a std::ostream using a pointer to your stream buffer.

Can't I just replace the buffer of some stream with a instance of a subclass of std::basic_streambuf

No, not replace, but construct one. You construct a std::istream or a std::ostream, using a pointer to your buffer. You will not use a std::[io]fstream, but rather std::istream and std::ostream, constructed using your stream buffer.

All that a std::ifstream is, for example, is a subclass of std::istream that constructs its superclass with a pointer to an internal stream buffer that reads from a file.

Feel free to create your own subclass of std::istream, that multiply-inherits from your stream buffer subclass, and std::istream, constructs the stream buffer subclass first, then the std::istream.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Thanks for your help. Please see my edit. Is this correct ? Here I will have two buffers, one in each base class of my basic_sockstream class. Is that correct ? Why having a buffer for input and another one for output when a single buffer supports both operations ? Also one more question : the data formatting (string/number conversions, etc) is independent from where the data is sent to. So why isn't there a single "top level" stream class that would use different streambuf implementations instead of having multiple "top level" stream classes like stringstream and fstream ? – Virus721 Jan 20 '17 at 23:34
  • Not quite correct. The superclass gets constructed first, using a pointer to a class member that's not constructed yet. The superclass gets constructed before the class member. Although this is unlikely to cause any actual problem, this is technically a bug, and is undefined behavior. There's a reason I referenced multiple inheritance -- you should inherit from your stream buffer first, then the stream class, so that the stream buffer gets constructed first. And, yes, you can -- and you should -- use a single stream buffer for both input and output operations. – Sam Varshavchik Jan 20 '17 at 23:38
  • Now I'm not sure about how to do this. Where would you place the `streambuf` ? In the "top level" `basic_sockstream` class ? In the constructor of `basic_sockstream`, I first have to call parent constructors and provide them with a `streambuf`, which I do not have at that point, to be forwarded to the constructors of `basic_istream` and `basic_ostream`. On the other end, I if put that buffer into `basic_isockstream` and `basic_osockstream`, I will end up having two buffers. – Virus721 Jan 20 '17 at 23:47
  • As I explained: multiple inheritance. Inherit from both streambuf, and `std::ostream`. `class basic_osockstream : public basic_sock_streambuf< ... >, public std::basic_ostream< char_type, traits_type >`. Constructor: `basic_osockstream(): parent(this)`. – Sam Varshavchik Jan 20 '17 at 23:52
  • I see. But then, just like `fstream` inherits from `ifstream` and `ofstream`, If `basic_sockstream` inherits from the `basic_osockstream` you just described, and also from an equivalent `basic_isockstream` (which would symetrically inherit from `basic_sock_streambuf` and `basic_istream`), I will get two buffers, wouldn't I ? – Virus721 Jan 20 '17 at 23:57
  • Well I just checked the definition of basic_fstream and it inherits from iostream (and not just ostream) and stream_fieldbuf , so I guess I understand now. – Virus721 Jan 21 '17 at 00:02
  • I have updated my code in the question. I think it is correct now. Can you please mention the inheritance from basic_iostream and basic_streambuf in your answer ? – Virus721 Jan 21 '17 at 00:08
  • Multiple inheritance of this kind is one way to do it, but not the only one. The C++ standard does not require `std::fstream` to inherit from `std::basic_filebuf`, only from `std::iostream`. Individual C++ implementations have their own means of correctly piecing together a jigsaw puzzle. You, on the other hand, are free to glue together your classes in whichever way makes sense for you, and the approach involving multiple inheritance is simply the easiest one. – Sam Varshavchik Jan 21 '17 at 00:22