3
\$\begingroup\$

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_size> bitset;
    std::size_t current_bit;

    std::ofstream &_stream;

    void flush() {
        for (auto i = current_bit; i < bitset_size; ++i) {
            bitset[i] = false;
        }

        auto buffer = bitset.to_ulong();
        while (buffer != 0) {
            _stream.write(reinterpret_cast<char *>(&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;
    }
};
\$\endgroup\$
2
\$\begingroup\$

I would not write an adapter for a stream.
But rather a stream manipulator.

Try this:

#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);
    }
};

Example:

#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";
}
\$\endgroup\$
  • \$\begingroup\$ thx. However, when you're writing a templated and a non-templated version of operator<<, why don't you use a template specialization? I understand that a non-templated version has precedence but so does a template specialization. \$\endgroup\$ – alisianoi Nov 6 '14 at 5:38
  • \$\begingroup\$ moreover, I tried googling for adapter for a stream (got results about java) and manipulator (got manipulators from <ios>). What do you mean by your first two lines of the answer? \$\endgroup\$ – alisianoi Nov 6 '14 at 5:40
  • 1
    \$\begingroup\$ why don't you use a template specialization? No major reason. Its more to do with the reading logic of the person coming after me. The non template version is the one were I am actually doing stuff. The templated one is "everything" else and falls back to normal mode. \$\endgroup\$ – Martin York Nov 6 '14 at 19:01
  • 1
    \$\begingroup\$ I was thinking of manipulators like std::setw and std::flush that indcue a different behavior in a stream for a certain period. look at #include <iomanip> \$\endgroup\$ – Martin York Nov 6 '14 at 19:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.