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.

3
  • stackoverflow.com/questions/1150843/binary-version-of-iostream Commented Apr 28, 2014 at 6: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. Commented Apr 28, 2014 at 6: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. Commented Apr 28, 2014 at 8:42

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

1 Comment

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

9 Comments

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 Perhaps you could overload a different operator? see EDIT.
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.
This violates the basic contracts of <<, and should not be done.
@vmrob The contract for << for std::ostream is that the output will be formatted text.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.