As a rule you should only use operator overloading when it is natural. If you're scratching around for a suitable operator for some functionality then it's a good sign that you shouldn't be forcing operator overloading on your problem.
Having said that, what you are trying to do is have a proxy object that dispatches read and write events to one of a pair of objects. Proxying object frequently overload the ->
operator to give pointer-like semantics. (You can't overload .
.)
While you could have two overloads of ->
differentiated by const
-ness, I would caution against this as it is problematic for read actions. The overload is selected by whether the object is referenced through a const or non-const reference and not whether the action is a actually read or a write. This fact makes the approach error prone.
What you can do is split access from the storage and create a multi-buffer class template and a buffer accessor template that accesses the appropriate member, using operator->
for syntactic ease.
This class stores multiple instances of the template parameter T
and stores an offset so that various accessors can retrieve the front/active buffer or other buffers by relative offset. Using a template parameter of n == 1
means that there is only one T
instance and multi-buffering is effectively disabled.
template< class T, std::size_t n >
struct MultiBuffer
{
MultiBuffer() : _active_offset(0) {}
void ChangeBuffers() { ++_active_offset; }
T* GetInstance(std::size_t k) { return &_objects[ (_active_offset + k) % n ]; }
private:
T _objects[n];
std::size_t _active_offset;
};
This class abstracts the buffer selection. It references the MultiBuffer
via reference so you must guarantee that its lifetime is short than the MultiBuffer
that it uses. It has it's own offset which is added to the MultiBuffer
offset so that different BufferAccess
can reference different members of the array (e.g. template parameter n = 0 for front buffer access and 1 for back buffer access).
Note that the BufferAccess
offset is a member and not a template parameter so that methods that operate on BufferAccess
objects aren't tied to only working on one particular offset or having to be templates themselves. I've made the object count a template parameter as, from your description it's likely to be a configuration option and this gives the compiler the maximum opportunity for optimization.
template< class T, std::size_t n >
class BufferAccess
{
public:
BufferAccess( MultiBuffer< T, n >& buf, std::size_t offset )
: _buffer(buf), _offset(offset)
{
}
T* operator->() const
{
return _buffer.GetInstance(_offset);
}
private:
MultiBuffer< T, n >& _buffer;
const std::size_t _offset;
};
Putting it all together with a test class, note that by overloading ->
we can easily call the members of the test class from the BufferAccess
instance without the BufferAccess
needing any knowledge of what members the test class has.
Also not the a single change switches between single and double buffering. Triple buffering is also trivial to achieve if you could find a need for it.
class TestClass
{
public:
TestClass() : _n(0) {}
int get() const { return _n; }
void set(int n) { _n = n; }
private:
int _n;
};
#include <iostream>
#include <ostream>
int main()
{
const std::size_t buffers = 2;
MultiBuffer<TestClass, buffers> mbuf;
BufferAccess<TestClass, buffers> frontBuffer(mbuf, 0);
BufferAccess<TestClass, buffers> backBuffer(mbuf, 1);
std::cout << "set front to 5\n";
frontBuffer->set(5);
std::cout << "back = " << backBuffer->get() << '\n';
std::cout << "swap buffers\n";
++mbuf.offset;
std::cout << "set front to 10\n";
frontBuffer->set(10);
std::cout << "back = " << backBuffer->get() << '\n';
std::cout << "front = " << frontBuffer->get() << '\n';
return 0;
}