HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Implementing binary output to a file in C++11

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
fileimplementingbinaryoutput

Problem

I would like to have a class which supports outputting bits into a file. This is what I came up with but I fear there are many poor decisions. Should I inherit from a std::ofstream instead of keeping it as a data member?

class OFBitStream final
{
    static const std::size_t bitset_size = 8 * sizeof(unsigned long);
    std::bitset bitset;
    std::size_t current_bit;

    std::ofstream &_stream;

    void flush() {
        for (auto i = current_bit; i (&buffer), 1);
            buffer >>= 8;
        }
    }
 public:
    OFBitStream(std::ofstream &stream)
        : bitset(0), current_bit(0), _stream(stream) {}

    ~OFBitStream() {
        flush();
        _stream.close();
    }

    friend OFBitStream &operator<<(OFBitStream &dst, const bool &data)
    {
        dst.bitset[dst.current_bit++] = data;

        if (dst.current_bit == dst.bitset_size) {
            dst.flush();
            dst.current_bit = 0;
        }

        return dst;
    }
};

Solution

I would not write an adapter for a stream.

But rather a stream manipulator.

Try this:

#include 

// The object that does all the work.
class BBoolWrapper
{
    std::ostream&          str;
    mutable std::size_t    position;
    mutable std::bitset data;

    public:
        BBoolWrapper(std::ostream& str)
            : str(str)
            , position(0)
        {}
        ~BBoolWrapper()
        {
            // If there is data to be flushed when we destroy the
            // object then make sure it goes on the stream.
            flush();
        }

        // Testing state.
        bool empty() const      { return position == 0;}
        bool full() const       { return position == data.size();}

        // Just the way I do it.
        // You can add bits any way you like one at a time.
        void addBit(bool nextbit) const
        {
            data (&value), sizeof(unsigned long));
                position  = 0;
            }
        }

    // The interesting part.
    // If we have an object like this and a bool using the `
    friend std::ostream& operator<<(BBoolWrapper const& stream, T const& val)
    {
        stream.flush();
        return stream.str << val;
    }
};

// A simple empty class to start the streaming of bits
// via the above wrapper class.
struct BBool
{
    friend BBoolWrapper operator<<(std::ostream& stream, BBool const&)
    {
        return BBoolWrapper(stream);
    }
};


Example:

#include 
#include 

int main()
{

    // Use the BBool() object on a stream to turn binary mode on.
    // It stays on until you add a non boolean object to the chain. (like "\n")
    std::ofstream log("log");
    log       << BBool() << false << true << false << false << false << false << false << true << "\n";
    std::cout << BBool() << false << true << false << false << false << false << false << true << "\n";
}

Code Snippets

#include <ostream>

// The object that does all the work.
class BBoolWrapper
{
    std::ostream&          str;
    mutable std::size_t    position;
    mutable std::bitset<sizeof(unsigned long) * CHAR_BIT> data;

    public:
        BBoolWrapper(std::ostream& str)
            : str(str)
            , position(0)
        {}
        ~BBoolWrapper()
        {
            // If there is data to be flushed when we destroy the
            // object then make sure it goes on the stream.
            flush();
        }

        // Testing state.
        bool empty() const      { return position == 0;}
        bool full() const       { return position == data.size();}

        // Just the way I do it.
        // You can add bits any way you like one at a time.
        void addBit(bool nextbit) const
        {
            data <<= 1;
            data[0] = nextbit;
            ++position;
            if (full())
            {
                flush();
            }
        }

        // Simple Flush to the stream (if there is any data).
        void flush() const
        {
            if (!empty())
            {
                unsigned long value = data.to_ulong();
                str.write(reinterpret_cast<char*>(&value), sizeof(unsigned long));
                position  = 0;
            }
        }

    // The interesting part.
    // If we have an object like this and a bool using the `<<` operator
    // Then add the bit and return this object so we are chaining the
    // `<<` operations.
    friend BBoolWrapper const& operator<<(BBoolWrapper const& stream, bool nextbit)
    {
        stream.addBit(nextbit);
        return stream;
    }

    // If we use any other type with `<<` operator then flush this object.
    // Return the underlying stream object to get back to normal stream
    // operations.
    template<typename T>
    friend std::ostream& operator<<(BBoolWrapper const& stream, T const& val)
    {
        stream.flush();
        return stream.str << val;
    }
};

// A simple empty class to start the streaming of bits
// via the above wrapper class.
struct BBool
{
    friend BBoolWrapper operator<<(std::ostream& stream, BBool const&)
    {
        return BBoolWrapper(stream);
    }
};
#include <iostream>
#include <ifstream>

int main()
{

    // Use the BBool() object on a stream to turn binary mode on.
    // It stays on until you add a non boolean object to the chain. (like "\n")
    std::ofstream log("log");
    log       << BBool() << false << true << false << false << false << false << false << true << "\n";
    std::cout << BBool() << false << true << false << false << false << false << false << true << "\n";
}

Context

StackExchange Code Review Q#69007, answer score: 2

Revisions (0)

No revisions yet.