Grebu
Grebu

Reputation: 205

Decoupling from template parameter without dynamic polymorphism

I wrote a Ringbuffer class, which among others is used to buffer data for serial transmission. The size of the buffer is defined by a template parameter BUFFER_SIZE. This keeps the buffer versatile while avoiding dynamic memory allocation. For example the receiving and transmitting buffer can thus be sized differently and the memory usage is known at compile time. The target is a small microcontroller (AVR).

Simplified representation of the Ringbuffer class:

template<int BUFFER_SIZE>
class Ringbuffer
{
    protected:
    uint8_t _buffer[BUFFER_SIZE];
    public:
    void push(uint8_t);
    uint8_t getNext();
}

A class using the Ringbuffer (e.g. classes for framing or controlling the UART) should not need to know the template parameter BUFFER_SIZE of the Ringbuffer.
This could easily be achieved using dynamic polymorphism. However I wish to avoid the cost of dynamic polymorphism (virtual function lookup, missing compiler checks).

Is there a different way to decouple the access to a template class from its template parameters?
Contrary to dynamic polymorphism the use of static polymorphism (using the curiously recurring template pattern) can not solve this issue.

The limited ressources of the microcontroller are the main reasons why I wish to avoid dynamic memory allocation and dynamic polymorphism.
C++ for AVR is somehow limited, but answers can ignore that.

Upvotes: 2

Views: 113

Answers (1)

Kamil Flaga
Kamil Flaga

Reputation: 106

Will this solution work for you?

// Pass pointer to `Ringbuffer` to use in other classes
class Ringbuffer 
{
    uint8_t* _bufPtr; // Downside is you need to store additional 2 fields
    int _bufSize;
protected:
    constexpr Ringbuffer(uint8_t* bufPtr, int bufSize) : _bufPtr(bufPtr), _bufSize(bufSize) { }

    template <std::size_t N>
    constexpr Ringbuffer(uint8_t (&buffer)[N]) : _bufPtr(buffer), _bufSize(N) {}

    constexpr Ringbuffer(const Ringbuffer&) = default;
    Ringbuffer& operator=(const Ringbuffer&) = default;
public:
    void push(uint8_t); // Work with _bufPtr / _bufSize instead of _buffer
    uint8_t getNext();
};

// Passed pointers to `Ringbuffer` will actually point to `RingbufferFinal`
// with static buffer with parametrized size
template<int BUFFER_SIZE>
class RingbufferFinal : public Ringbuffer 
{
protected:
    uint8_t _buffer[BUFFER_SIZE];

public:
    constexpr RingbufferFinal() : Ringbuffer(_buffer) {}

    constexpr RingbufferFinal(const RingbufferFinal<BUFFER_SIZE>& other) :
        Ringbuffer(*this)
    {
        // Copy contents of other._buffer only.
        // _bufPtr of both buffers remains unchanged.
        memcpy(_buffer, other._buffer, BUFFER_SIZE);
    }

    constexpr RingbufferFinal& operator=(const RingbufferFinal<BUFFER_SIZE>& other)
    {
        memcpy(_buffer, other._buffer, BUFFER_SIZE);
        return *this;
    }
};

Edit: As Jarod42 pointed out previous solution failed on copying, so I added copy ctors for correctness. I guess that copying content between buffers is proper behavior in this case and base class shouldn't be allowed to be copied.

Upvotes: 2

Related Questions