4

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.

vmrob
  • 2,966
  • 29
  • 40
  • http://stackoverflow.com/questions/1150843/binary-version-of-iostream – oakad Apr 28 '14 at 06:26
  • 1
    iostream is not designed with this usage in mind. You may want to consider other libraries, such as boost::spirit::karma or boost::serialize. – oakad Apr 28 '14 at 06:27
  • @oakad Or if you do want to use the `>>` and `<<` formats (say because you're not serializing, but reading arbitrary data in a binary format), it's fairly easy to define new stream classes whose "contract" is some sort of binary format, rather than text. – James Kanze Apr 28 '14 at 08:42

2 Answers2

3

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.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • I appreciate the answer. I was hoping to accomplish my needs without the sort of complexity, but so long as the complexity is isolated and the interface clean, it fits my overall goal. In the end, I decided to use the boost serialization framework over designing my own interface. – vmrob Apr 30 '14 at 07:49
1

You could declare an operator << for ostreams (so not just string streams, but also file streams). Something like the following might work (untested), but you might run into troubles with the type (uint32_t):

std::ostream& operator<<(std::ostream& stream, uint32_t value)
{
    stream.write(reinterpret_cast<char*>(&value), sizeof(value));
    return stream;
}

std::stringstream file;
file << version << dataSize << recordCount;

EDIT:

Because of the type value has, the << operator is already defined. One alternative would be to declare a new operator <=:

std::ostream& operator<=(std::ostream& stream, uint32_t value);
file <= version <= dataSize <= recordCount;

Both operators operate in a left-to-right fashion, so this might work, maybe not the nicest solution though.

ilent2
  • 5,171
  • 3
  • 21
  • 30
  • As you thought, the type does give a little bit of trouble using that solution. `uint32_t` is a typedef for unsigned int on my system and an overload already exists for that type. If I was looking to use a custom class/type, however, that would work very well. – vmrob Apr 28 '14 at 06:28
  • @vmrob Perhaps you could overload a different operator? see EDIT. – ilent2 Apr 28 '14 at 06:37
  • 1
    I think that would work. After evaluating my code, I've decided to take a look at the boost::serialize library. It seems to do what you've suggested by overloading an operator (the `&` specifically) and has many other features I think will work well for my program. – vmrob Apr 28 '14 at 07:04
  • 1
    This violates the basic contracts of `<<`, and should _not_ be done. – James Kanze Apr 28 '14 at 08:32
  • Can you even overload `<=`? – David G Apr 28 '14 at 11:20
  • @JamesKanze Could you elaborate on the contract you're referring to? – vmrob Apr 30 '14 at 07:52
  • @0x499602D2 Yes you can overload the `<=` operator. A quick Google search yields this tutorial: http://www.learncpp.com/cpp-tutorial/94-overloading-the-comparison-operators/ – vmrob Apr 30 '14 at 07:53
  • 1
    @vmrob The contract for `<<` for `std::ostream` is that the output will be formatted text. – James Kanze Apr 30 '14 at 08:13
  • @0x499602D2 Of course you can overload `<=`. It's actually rather frequent to do so---any class which supports ordered comparison should do so. Of course, overloading it to do something other than ordered comparison is flagrant operator abuse. – James Kanze Apr 30 '14 at 08:15