I'm working on writing some test cases for a class that will presumably read from an std::istream
and write to an std::ostream
. As part of the testing process, I would like to manually create a block of test file data, wrap it in a std::stringstream
, and then pass it to my class for processing.
I feel like my current solution lacks is lacking despite the fact that it does work. I really don't like using those raw write calls with reinterpret_cast
.
std::stringstream file;
uint32_t version = 1;
uint32_t dataSize = 10;
uint32_t recordCount = 3;
file.write(reinterpret_cast<char*>(&version), sizeof(version));
file.write(reinterpret_cast<char*>(&dataSize), sizeof(dataSize));
file.write(reinterpret_cast<char*>(&recordCount), sizeof(recordCount));
myclass.read(file)
Is there a way I can use the stream operators to write this data in binary form? I'm hoping for something more akin to the following.
std::stringstream file;
uint32_t version = 1;
uint32_t dataSize = 0;
uint32_t recordCount = 3;
file << version << dataSize << recordCount;
myclass.read(file);
If I go this route, extracting a number results in 103
which is expected in an ascii context, but I'm obviously trying to avoid serializing my data in that manner.
There is a problem with your code: when you use the
reinterpret_cast
, you don't actually know what you are writing
to the stream, so you don't know what you are testing. If you
want to test how your code reacts to a stream of bytes in
a binary format, you can easily initialize an
std::istringstream
with an arbitrary stream of bytes:
char bytes[] = { /*...*/ };
std::istringstream( std::string( std::begin( bytes ), std::end( bytes ) ) );
(If you don't have C++11, you can easily write your own begin
and end
.)
In this way, you'll know exactly what the bytes are, rather than depending on the aleas of how your implementation represents any specific type.
Alternatively: if you're reading and writing binary data, you
may want to define classes which do it, using >>
and <<
.
Such classes would be unrelated to std::istream
and
std::ostream
, but could logically use std::ios_base
to
provide support for the conventional error reporting and the
interface to std::streambuf
. The class would then have
members something like the following:
namespace {
class ByteGetter
{
public:
explicit ByteGetter( ixdrstream& stream )
: mySentry( stream )
, myStream( stream )
, mySB( stream->rdbuf() )
, myIsFirst( true )
{
if ( ! mySentry ) {
mySB = NULL ;
}
}
std::uint8_t get()
{
int result = 0 ;
if ( mySB != NULL ) {
result = mySB->sgetc() ;
if ( result == EOF ) {
result = 0 ;
myStream.setstate( myIsFirst
? std::ios::failbit | std::ios::eofbit
: std::ios::failbit | std::ios::eofbit | std::ios::badbit ) ;
}
}
myIsFirst = false ;
return result ;
}
private:
ixdrstream::sentry mySentry ;
ixdrstream& myStream ;
std::streambuf* mySB ;
bool myIsFirst ;
} ;
}
ixdrstream&
ixdrstream::operator>>( std::uint32_t& dest )
{
ByteGetter source( *this ) ;
std::uint32_t tmp = source.get() << 24 ;
tmp |= source.get() << 16 ;
tmp |= source.get() << 8 ;
tmp |= source.get() ;
if ( *this ) {
dest = tmp ;
}
return *this ;
}
(For a maximum of portability, you might wont to avoid the
uint8_t
and uint32_t
. At this level, writing code without
knowing the exact size of the type is a little more difficult,
so if you are certain you'll never have to port to an exotic
system where they may not be defined, it's probably worth saving
yourself the extra work.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With