How do I create my own ostream/streambuf?
The canonical approach consists in defining your own streambuf.
You should have a look at:
- Angelika LAnger's articles on IOStreams derivation
- James Kanze's articles on filtering streambufs
- boost.iostream for examples of application
A custom ostream
A custom destination for ostream means implementing your own ostreambuf. If you want your streambuf to actually buffer (i.e. don't connect to the database after each character), the easiest way to do that is by creating a class inheriting from std::stringbuf
. The only function that you'll need to override is the sync()
method, which is being called whenever the stream is flushed.
class MyBuf : public std::stringbuf
{
public:
virtual int sync() {
// add this->str() to database here
// (optionally clear buffer afterwards)
}
};
You can then create a std::ostream
using your buffer:
MyBuf buff;
std::ostream stream(&buf)
Most people advised against redirecting the stream to a database, but they ignored my description that the database basically has a single blob field where all text is going to.
In rare cases, I might send data to a different field. This can be facilitated with custom attributes understood by my stream. For example:
MyStream << "Some text " << process_id(1234) << "more text" << std::flush
The code above will create a record in the database with:
blob: 'Some text more text'
process_id: 1234
process_id()
is a method returning a structure ProcessID
. Then, in the implementation of my ostream, I have an operator<<(ProcessID const& pid)
, which stores the process ID until it gets written. Works great!
Customize streambuffer for C++ ostream
The problem, it seems, is that your object d
is destroyed before std::cout
, and thus the final calls for destructing the global object, which include flushing buffers, and that take palce after the end of main()
(remember it's a global object), attempt to perform operations on a no longer-extant streambuf
object. Your buffer object definitely should outlive the stream you associate it with.
One way of having this in you program is to make d
into a pointer, which you will never delete. Alternatively, you can keep your local object as you used it, but call std::cout.flush()
, and then assign cout
's buffer to something else (even nullptr
) before going out of scope.
While testing with your program (and before I found the problem), I made small changes that made sense to me. For example, after you successfully write to the descriptor, you can simply bump(ret)
(you already know that ret!=-1
, so its safe to use).
Other changes that I didn't make, but which you could consider, are to have the descriptor set by the constructor itself, having the destructor close a dangling descriptor, and perhaps change dynamic allocation from C-oriented malloc()
/realloc()
/free()
to C++-oriented std::vector
.
Speaking of allocation, you made a very common mistake when using realloc()
. If the reallocation fails, realloc()
will keep the original pointer intact, and signal the failure by returning a null pointer. Since you use the same pointer to get the return value, you risk losing the reference to a still allocated memory. So, if you at all cannot use C++ containers instead of C pointers, you should change you code to something more like this:
char *newptr;
newptr=static_cast<char *>(realloc(ptr, newsize));
if(newptr)
ptr=newptr;
else {
// Any treatment you want. I wrote some fatal failure code, but
// you might even prefer to go on with current buffer.
perror("ralloc()");
exit(1);
}
creating an ostream
As it is for education, as you say, i will show you how i would do such a thingy. Otherwise, stringstream
is really the way to go.
Sounds like you want to create a streambuf implementation that then writes to a vector / deque. Something like this (copying from another answer of me that targeted a /dev/null stream):
template<typename Ch, typename Traits = std::char_traits<Ch>,
typename Sequence = std::vector<Ch> >
struct basic_seqbuf : std::basic_streambuf<Ch, Traits> {
typedef std::basic_streambuf<Ch, Traits> base_type;
typedef typename base_type::int_type int_type;
typedef typename base_type::traits_type traits_type;
virtual int_type overflow(int_type ch) {
if(traits_type::eq_int_type(ch, traits_type::eof()))
return traits_type::eof();
c.push_back(traits_type::to_char_type(ch));
return ch;
}
Sequence const& get_sequence() const {
return c;
}
protected:
Sequence c;
};
// convenient typedefs
typedef basic_seqbuf<char> seqbuf;
typedef basic_seqbuf<wchar_t> wseqbuf;
You can use it like this:
seqbuf s;
std::ostream os(&s);
os << "hello, i'm " << 22 << " years old" << std::endl;
std::vector<char> v = s.get_sequence();
If you want to have a deque as sequence, you can do so:
typedef basic_seqbuf< char, char_traits<char>, std::deque<char> > dseq_buf;
Or something similar... Well i haven't tested it. But maybe that's also a good thing - so if it contains still bugs, you can try fixing them.
Wrapping FILE* with custom std::ostream
As Ben Voigt points out, you want to subclass streambuf
. There are pages on the University of Southern California's website which have the documentation, header, and source for a GNU implementation of a streambuf
subclass (stdiobuf
) that wraps a FILE*
. It has some dependencies on the library it is a part of (GroovX), but those should be easily to remove (I would begin by removing all references to GVX_TRACE
).
Interestingly, it also provides a minimalistic subclass (stdiostream
) of std::iostream
, in spite of what Ben Voigt said. But this does not seem to be necessary, as the rdbuf ("read buffer"/set the stream buffer) method which the stdiostream
class uses to connect the stdiobuf
class to a stream object is publicly accessible.
You can find more about subclassing streambuf
here (look particularly at the bottom of the page, which discussing the virtual functions). The implementation linked above overrides sync
, underflow
(to support input) and overflow
(to support output).
Further notes about the linked implementation:
- The
init
method uses thesetg
andsetp
methods to set the pointers for the input and output sequences. - The line
const int num = pptr()-pbase();
is calculating the number of characters to flush by subtracting the base output pointer from the current output pointer ("put pointer"). - The variable unhelpfully named
om
is the mode parameter. - The variable named
fd
is the file descriptor.
Custom streambuffer in std::ofstream
The concrete file stream classes have their own rdbuf()
method that takes 0 arguments and it hides the other rdbuf()
method inherited from the virtual base std::basic_ios
. Qualifying the name to lookup the base class method should work:
std::ofstream ofs;
ofs.basic_ios<char>::rdbuf(example.rdbuf());
Have a C++ Class act like a custom ostream, sstream
try this:
class MyObject {
public:
template <class T>
MyObject &operator<<(const T &x) {
s << ':' << x << ':';
return *this;
}
std::string to_string() const { return s.str(); }
private:
std::ostringstream s;
};
MyObject obj;
obj << "Hello" << 12345;
std::cout << obj.to_string() << std::endl;
There are certain things you won't be able to shove into the stream, but it should work for all the basics.
How to write custom input stream in C++
The proper way to create a new stream in C++ is to derive from std::streambuf
and to override the underflow()
operation for reading and the overflow()
and sync()
operations for writing. For your purpose you'd create a filtering stream buffer which takes another stream buffer (and possibly a stream from which the stream buffer can be extracted using rdbuf()
) as argument and implements its own operations in terms of this stream buffer.
The basic outline of a stream buffer would be something like this:
class compressbuf
: public std::streambuf {
std::streambuf* sbuf_;
char* buffer_;
// context for the compression
public:
compressbuf(std::streambuf* sbuf)
: sbuf_(sbuf), buffer_(new char[1024]) {
// initialize compression context
}
~compressbuf() { delete[] this->buffer_; }
int underflow() {
if (this->gptr() == this->egptr()) {
// decompress data into buffer_, obtaining its own input from
// this->sbuf_; if necessary resize buffer
// the next statement assumes "size" characters were produced (if
// no more characters are available, size == 0.
this->setg(this->buffer_, this->buffer_, this->buffer_ + size);
}
return this->gptr() == this->egptr()
? std::char_traits<char>::eof()
: std::char_traits<char>::to_int_type(*this->gptr());
}
};
How underflow()
looks exactly depends on the compression library being used. Most libraries I have used keep an internal buffer which needs to be filled and which retains the bytes which are not yet consumed. Typically, it is fairly easy to hook the decompression into underflow()
.
Once the stream buffer is created, you can just initialize an std::istream
object with the stream buffer:
std::ifstream fin("some.file");
compressbuf sbuf(fin.rdbuf());
std::istream in(&sbuf);
If you are going to use the stream buffer frequently, you might want to encapsulate the object construction into a class, e.g., icompressstream
. Doing so is a bit tricky because the base class std::ios
is a virtual base and is the actual location where the stream buffer is stored. To construct the stream buffer before passing a pointer to a std::ios
thus requires jumping through a few hoops: It requires the use of a virtual
base class. Here is how this could look roughly:
struct compressstream_base {
compressbuf sbuf_;
compressstream_base(std::streambuf* sbuf): sbuf_(sbuf) {}
};
class icompressstream
: virtual compressstream_base
, public std::istream {
public:
icompressstream(std::streambuf* sbuf)
: compressstream_base(sbuf)
, std::ios(&this->sbuf_)
, std::istream(&this->sbuf_) {
}
};
(I just typed this code without a simple way to test that it is reasonably correct; please expect typos but the overall approach should work as described)
Related Topics
How to Use an Array as Map Value
Class Template Argument Deduction Not Working with Alias Template
Why Doesn't Std::String Provide Implicit Conversion to Char*
Converting Epoch Time to "Real" Date/Time
What Is the Behavior of "Delete" with Stack Objects
Std::Unique_Ptr for C Functions That Need Free
How to Make a Portable Isnan/Isinf Function
Get Function Pointer from Std::Function When Using Std::Bind
Overloading Base Class Method in Derived Class
Should We Generally Use Float Literals for Floats Instead of the Simpler Double Literals
Constexpr Not Compiling in Vc2013
Getting a Vector<Derived*> into a Function That Expects a Vector<Base*>
Howto Create Combinations of Several Vectors Without Hardcoding Loops in C++