patterncppMinor
Memory streambuf and stream
Viewed 0 times
andstreambufstreammemory
Problem
There are a plethora of streams and streambuffers available today, none are as fast as a memory
streambuf, that you can use for testing. Shoot away!#ifndef MEMSTREAMBUF_HPP
# define MEMSTREAMBUF_HPP
# pragma once
#include
#include
#include
#include
#include
template
class memstreambuf : public ::std::streambuf
{
::std::array buf_;
public:
memstreambuf()
{
setbuf(buf_.data(), buf_.size());
}
::std::streambuf* setbuf(char_type* const s,
::std::streamsize const n) final
{
auto const begin(s);
auto const end(s + n);
setg(begin, begin, end);
setp(begin, end);
return this;
}
pos_type seekpos(pos_type const pos,
::std::ios_base::openmode const which = ::std::ios_base::in |
::std::ios_base::out) final
{
switch (which)
{
case ::std::ios_base::in:
if (pos
class memstream : public memstreambuf,
public ::std::istream,
public ::std::ostream
{
public:
memstream() : ::std::istream(this),
::std::ostream(this)
{
}
};
#endif // MEMSTREAMBUF_HPPSolution
Assert
I personally dislike using asserts. The problem for me is that they do different things in production and debug code. I want the same action in both.
So in this code if we get to the default action; the debug version will assert (and stop the application) while production code will return
In my opinion if you get to a point where your code should not then throw an exception that should not be caught. That will cause the application to exit (in a controlled way).
But in this case I think the expected behavior is not to throw but to return -1. So I would make the default action a no-op.
seekpos which
The which in your
xsgetn/xsputn eof
I think you return result of these functions can be incorrect. You should only return
Overall Design
The input and output use the same buffer but are not linked together. If you have not written anything into the buffer I would not expect you to be able to read from the buffer (as there is nothing to read).
When you do write I would only expect you to be able to read only what has written (no more).
Result:
So I wrote 6 characters. But I managed to read 10 characters. What were the last 4 characters?
Circular Buffer
Once you have linked the two buffers correctly. You could get more from the buffer by making it circular. That means as you get to the end of the write buffer you can circle around and start writing at the beginning again if you have been reading from the buffer and there is space.
Inheriting from the buffer
Normally I have seen this as a member object rather than inheriting from the buffer. Though this technique saves a call to setbuffer. I can live with it.
BUT: if you are going to inherit from it then I would use private inheritance.
This is because I don't want my object to behave like a stream and a stream buffer to people who use it. That could be confusing. So hide the buffer properties from external users (by using private). They can always get a reference to the buffer using the method
pragma once and include guards
There is no point in using both:
I would use the header guards because not all compiler support
Note the space between
I personally dislike using asserts. The problem for me is that they do different things in production and debug code. I want the same action in both.
pos_type seekpos(pos_type const pos, ::std::ios_base::openmode const which) final
{
switch (which)
{
case ::std::ios_base::in:
case ::std::ios_base::out:
default:
assert(0);
}
return pos_type(off_type(-1));
}So in this code if we get to the default action; the debug version will assert (and stop the application) while production code will return
-1.In my opinion if you get to a point where your code should not then throw an exception that should not be caught. That will cause the application to exit (in a controlled way).
But in this case I think the expected behavior is not to throw but to return -1. So I would make the default action a no-op.
seekpos which
The which in your
seekpos() has basically three settings. You only check for two of the three.switch (which)
{
case ::std::ios_base::in:
case ::std::ios_base::out:
// You forgot this case
case ::std::ios_base::in | ::std::ios_base::out:
// it means set the position of the input and output stream.
// since this is the default value of `which` I would expect
// this to happen most often (and would assert in your code).
default:
assert(0);
}
return pos_type(off_type(-1));
}xsgetn/xsputn eof
I think you return result of these functions can be incorrect. You should only return
eof if you did not get/put any values. You return eof if you have filled/emptied all the data.::std::streamsize xsgetn(char_type* const s,
::std::streamsize const count) final
{
// If there is no data to get then return eof
if (egptr() == gptr()) {
traits_type::eof();
}
auto const size(::std::min(egptr() - gptr(), count));
::std::memcpy(s, gptr(), size);
gbump(size);
return size; // return the number of bytes read.
}Overall Design
The input and output use the same buffer but are not linked together. If you have not written anything into the buffer I would not expect you to be able to read from the buffer (as there is nothing to read).
When you do write I would only expect you to be able to read only what has written (no more).
int main()
{
memstream buffer;
if (buffer.write("abcdef", 6))
{
std::cout << "Write OK\n";
}
char data[100];
if (buffer.read(data, 10))
{
auto c = buffer.gcount();
std::cout << "Count: " << c << "\n";
std::cout << std::string(data, data+10) << "\n";
}
}Result:
Write OK
Count: 10
abcdefSo I wrote 6 characters. But I managed to read 10 characters. What were the last 4 characters?
Circular Buffer
Once you have linked the two buffers correctly. You could get more from the buffer by making it circular. That means as you get to the end of the write buffer you can circle around and start writing at the beginning again if you have been reading from the buffer and there is space.
Inheriting from the buffer
template
class memstream :
public memstreambuf, // This is unusual
public ::std::istream,
public ::std::ostream
{
public:
memstream() : ::std::istream(this),
::std::ostream(this)
{
}
};Normally I have seen this as a member object rather than inheriting from the buffer. Though this technique saves a call to setbuffer. I can live with it.
BUT: if you are going to inherit from it then I would use private inheritance.
template
class memstream :
private memstreambuf, // Note the private
public ::std::istream,
public ::std::ostream
{
public:
memstream() : ::std::istream(this),
::std::ostream(this)
{
}
};This is because I don't want my object to behave like a stream and a stream buffer to people who use it. That could be confusing. So hide the buffer properties from external users (by using private). They can always get a reference to the buffer using the method
rdbuf().pragma once and include guards
There is no point in using both:
#ifndef MEMSTREAMBUF_HPP
# define MEMSTREAMBUF_HPP
# pragma once
....
#endifI would use the header guards because not all compiler support
#pragma once. Note the space between
# and the word is non standard. Most compilers may be forgiving about it but not all so I would not do it.# define MEMSTREAMBUF_HPP
^ // extra space.Code Snippets
pos_type seekpos(pos_type const pos, ::std::ios_base::openmode const which) final
{
switch (which)
{
case ::std::ios_base::in:
case ::std::ios_base::out:
default:
assert(0);
}
return pos_type(off_type(-1));
}switch (which)
{
case ::std::ios_base::in:
case ::std::ios_base::out:
// You forgot this case
case ::std::ios_base::in | ::std::ios_base::out:
// it means set the position of the input and output stream.
// since this is the default value of `which` I would expect
// this to happen most often (and would assert in your code).
default:
assert(0);
}
return pos_type(off_type(-1));
}::std::streamsize xsgetn(char_type* const s,
::std::streamsize const count) final
{
// If there is no data to get then return eof
if (egptr() == gptr()) {
traits_type::eof();
}
auto const size(::std::min(egptr() - gptr(), count));
::std::memcpy(s, gptr(), size);
gbump(size);
return size; // return the number of bytes read.
}int main()
{
memstream<1024> buffer;
if (buffer.write("abcdef", 6))
{
std::cout << "Write OK\n";
}
char data[100];
if (buffer.read(data, 10))
{
auto c = buffer.gcount();
std::cout << "Count: " << c << "\n";
std::cout << std::string(data, data+10) << "\n";
}
}Write OK
Count: 10
abcdefContext
StackExchange Code Review Q#138479, answer score: 8
Revisions (0)
No revisions yet.