4

I have a kind of buffer class that takes an std::vector as a constructor parameter and uses this vector to storage bytes. This class has methods for reading from and for writing to the buffer. However I would want to give a const std::vector to this buffer class and still be able to use the read functions while write functions should fail at compile time or at least throw an exception.

Only solution that I come up was something like this:

class Buffer
{
    public:
    Buffer(std::vector<uint8>& vec)
    { 
        this->writeVec = vec
        this->readVec = vec;
    }
    Buffer(std::vector<uint8> const& vec)
    {
        this->writeVec = null
        this->readVec = vec;
    }
    void write(uint8 i)
    {
        this->throwIfWriteVecNull();
        // do write to writeVec
    }
    uint8 read()
    {
        // do read from readVec
    }

    private:
    std::vector<uint8>& writeVec;
    std::vector<uint8> const& readVec;
}

Is there any way to achive this without separate writer and reader classes (It would separate similar logic to two different classes which is not nice) and with compile time check for write access? I don't want to use const_casts to any other unsafe hacks. Feel also free to suggest alternative pattern/architecture as a solution.

EDIT:

Thanks all for your answers. From the three answers user315052's facade pattern was closest to something what I wanted, since IMO const_cast or multiple pointers are worse than having few more classes. I also did bit more research and stumbled upon this SO q/a where templates are used to select between const and non-const type. Now I have something like the following and it works perfect, I get "no method error" on compile time if I try to call write on const version. The code got bit ugly beacause of the all templates and stuff but getting errors on compile time is much much better than exceptions.

template <typename BlockType, bool isMutable>
class BaseBuf : boost::noncopyable
{
public:
    typedef typename boost::mpl::if_c<isMutable, std::vector<BlockType>, std::vector<BlockType> const>::type VectorType;
    BaseBuf(VectorType& blocks) : blocks(blocks)
    {
    }
    void seekReadCursor(size_t pos)
    {
         // seek to pos
    }
    bool readBool() const
    {
         // do read from pos
    }
    //void read...
    //...
protected:

    VectorType& blocks;
};

template <typename BlockType>
class ImmuBuf : public BaseBuf<BlockType, false>
{
public:
    typedef BaseBuf<BlockType, false> Parent;
    typedef typename Parent::VectorType VectorType;
    ImmuBuf(VectorType& blocks) : Parent(blocks)
    {
    }
private:

};

template <typename BlockType>
class MutaBuf : public BaseBuf<BlockType, true>
{
public:
    typedef BaseBuf<BlockType, true> Parent;
    typedef typename Parent::VectorType VectorType;
    MutaBuf(VectorType& blocks) : Parent(blocks)
    {
    }
    // void resize()...
    void writeBool(bool b)
    {
        // do write
    }
    //void write...
    //...
private:

};
Community
  • 1
  • 1
user1124809
  • 445
  • 3
  • 8
  • What about specifying the `read()` method as `const` and have only one internal member for the vector? – πάντα ῥεῖ Aug 17 '12 at 21:44
  • Don't be afraid of creating two classes. One can easily inherit from the other, or share common code as in user315052's answer. That said, one class may be sufficient - it depends on how it is used. – Kaiged Aug 17 '12 at 22:18

3 Answers3

1

I am not sure about the design, but we can try to work something similar to what you ask (whether it is a good idea or not is another discussion).

The first thing is that references cannot be NULL, if you want to provide for optional arguments, then you should use either pointers or higher level constructs (boost::optional). Then you can provide multiple constructors:

class Buffer {
   std::vector<uint8_t> const *readBuffer;
   std::vector<uint8_t>       *writeBuffer;
public:
   Buffer( std::vector<uint8_t>& v ) : readBuffer(&v), writeBuffer(&v) {}
   Buffer( std::vector<uint8_t> const & v ) : readBuffer(&v), writeBuffer() {}
   void write( uint8_t v );
   uint8_t read() const;
};

If the argument passed to the Buffer is const, then the writeBuffer pointer will not be set. You can test that by using if (writeBuffer) in you write function. Note that read should be marked as a const function as it does not modify the buffer.

That being said, you still need to work more on the design. The read and write functions as declared are probably not enough. What should be read/written? The first/last value? Should they append/consume data (in which case neither read nor the readBuffer should be const)...

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Thanks for pointing out errors! I should have used pointers as you said! My "real" class contains something like 20 write and read methods for integers of different size and endianess. Corresponding write and read methods share logic about the datatype they handle and that's why I don't want separate writer and reader classes. Good point that read() could consume data! In my design it does not and can therefore be const. – user1124809 Aug 17 '12 at 22:04
1

It really seems like you want a Buffer to be a facade for a read/write version and a read-only version.

class BufferInterface {
    friend class Buffer;
    friend class std::default_delete<BufferInterface>;
protected:
    virtual ~BufferInterface () {}
    virtual void write (uint8_t) = 0;
    virtual uint8_t read () = 0;
    //...
};

class Buffer {
    std::unique_ptr<BufferInterface> impl_;
public:
    Buffer (std::vector<uint8_t> &v) : impl_(new BufferReadWrite(v)) {}
    Buffer (const std::vector<uint8_t> &v) : impl_(new BufferReadOnly(v)) {}
    void write(uint8_t i) { impl_->write(i); }
    uint8_t read () { return impl_->read(); }
    //...
};

BufferInterface can implement any common logic to be reused by both the read-only version and the read/write version.

jxh
  • 69,070
  • 8
  • 110
  • 193
0

Const casts are in the language for a reason. They should be used sparingly, but I think this is one of those cases. I would do it something like this (not syntax checked):

Buffer(std::vector<uint8> const& vec)
{
    this->vec = const_cast<std::vector<uint8>& >(vec);
    this->readonly = true;
}
void write(uint8 i)
{
    this->throwIfReadOnly();
    // do write to vec
}
uint8 read() const
{
    // do read from vec
}

Note the addition of const on the read() method. If you want the compiler to additionally enforce const correctness, construct a read-only buffer like this:

const Buffer* buffer = new Buffer(vec);

That makes it only allowed to call the const methods. That's about as safe as you get without writing two completely separate classes.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94