How do I create different interfaces for one object?

I know how to create a single interface that can be applied to various objects. This is not what I am looking for. I want the opposite behavior: I want to find a mechanism to expose different interfaces to the same object without paying for virtual calls.

Essentially, let's say I have a queue, with PushMessage, PopMessage and IsEmpty methods:

class Queue
{
  public:
    void PushMessage(Message message);
    Message PopMessage();
    bool IsEmpty() const;
}

      

I want to pass someone a handle to this queue, but let them call PopMessage and IsEmpty and pass someone else a handle to the same queue, which only allows them to call PushMessage. Thus, the owner of the queue decides who can write to him and who can delete messages from him.

One of my ideas was to create two classes that do not contain their own data, 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 if it's safe. The owner of the original queue will have to go to EnQueueer or DeQueueer and I'm not sure if this is absolutely correct. Also, 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 only safe as long as the derived classes don't have their own members?

+3


source to share


1 answer


You can always use implicit conversions and have an object that wraps Queue

.

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 take advantage of the different implementations, use the magic of templates:



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 use C ++ concepts in templates if you like ... but not too necessary IMO.

Note that it EnQueueer

contains a reference to your queue, it is important that the lifetime of the queue spans the lifetime of the EnQueueer. You cannot store EnQueueer and let the Queue die. Safe usage involves replacing function calls and declarations that work great from Queue&

on EnQueueer

, etc.

+4


source







All Articles