Reputation: 1684
I know how to create a single interface that can be applied to different objects. That's not what I'm looking for. I want the opposite behavior: I want to find a mechanism to provide different interfaces to the same object, without paying for virtual calls.
Essentially, lets say I have a queue, with the methods PushMessage, PopMessage and IsEmpty:
class Queue
{
public:
void PushMessage(Message message);
Message PopMessage();
bool IsEmpty() const;
}
I want to hand someone a handle to this queue, but only let them call PopMessage and IsEmpty, and hand someone else a handle to the same queue that only lets them call PushMessage. So the owner of the queue decides who can write to it and who can remove messages from it.
One idea I had was to create two classes that hold no data members of their own, but inherit from Queue and provide proxy methods:
struct EnQueueer
: private Queue
{
void PushMessage(Message message){Queue::PushMessage(message);}
}
struct DeQueueer
: private Queue
{
Message PopMessage(){ return Queue::PopMessage();}
bool IsEmpty() const{ return Queue::IsEmpty();}
}
But I'm not sure this is safe. The owner of the original Queue would have to upcast to EnQueueer or DeQueueer, which I'm not sure is entirely correct. Moreover, the compiler didn't like it:
static_cast<DeQueueer*>(&m_queue); // Results in compile error:
// 'Queue' is an inaccessible base of 'DeQueueer'
reinterpret_cast solves the problem, but I'm not sure if it's safe. Is it safe only as long as the derived classes have no members of their own?
Upvotes: 3
Views: 128
Reputation: 3769
You can always take advantage of implicit conversions and have an object that wraps Queue
instead.
class EnQueueer {
public:
// implicitly convert to Queue
EnQueueer(const Queue& queue) : m_queue(queue) { }
operator Queue() cont { return m_queue; }
void PushMessage(Message m) { m_queue.PushMessage(m); }
private:
Queue& m_queue;
};
To make it take advantage of different implementations, use template magic:
template<typename Q>
class EnQueueer {
public:
// implicitly convert to Queue
EnQueueer(const Q& queue) : m_queue(queue) { }
operator Q() cont { return m_queue; }
void PushMessage(Message m) { m_queue.PushMessage(m); }
private:
Q& m_queue;
};
You can take advantage of C++ concepts in templates if you want... but not too necessary IMO.
Note that EnQueueer
contains a reference to your queue, this it is important for the lifetime of the queue to encompass the lifetime of EnQueueer. You can't store EnQueueer and let Queue die. Safe uses include substituting function calls and declarations that would work fine with Queue&
by EnQueueer
, etc.
Upvotes: 4