Design pattern to limit access to a class's public members in C++

Viewed 165

All of the code below is pseudo-code, for the sake of brevity.

Consider an event system, where have the following class:

class Event_dispatcher {
private:
    Listener_collection listeners;

public:
    void add_listener(Listener _listener) { ... }
    void remove_listener(Listener _listener) { ... }
    void post_event(Event _event) { ... }
};

I intend to have instances of Event_dispatcher in classes that are meant to send out events. However, I only want the events to be posted from inside the class, so that the user can only add or remove listeners, but never post events.
The simple solution would be to make Event_dispatcher a private member of the enclosing class, but then, I have to define add_listener and remove_listener as public members that forward the argument to the event dispatcher.
I am aware that two methods do not seem like a lot, but please bear in mind, this is just an example, I made it short on purpose. Imagine the same problem where I would have to define a significantly greater number of methods in a similar fashion.

I am trying to find out if there is a common or recommended way of solving problems like this. A solution that came to my mind would be to define an interface class like this:

class Event_dispatcher_interface {
private:
    Event_dispatcher* event_dispatcher;

public:
    void add_listener(Listener _listener) { event_dispatcher->add_listener(_listener); }
    void remove_listener(Listener _listener) { event_dispatcher->remove_listener(_listener); }
};

and make this a public member of the enclosing class. The pointer inside (*event_dispatcher) would point to the Event_dispatcher that's a private member of the enclosing class, so as to only allow for posting events from inside the class.

If there are other (better) solutions, I would appreciate any hints.

3 Answers

You might use inheritance to inherit from visibility

class Event_dispatcher {
private:
    Listener_collection listeners;
public:
    void add_listener(Listener _listener) { ... }
    void remove_listener(Listener _listener) { ... }
protected:
    void post_event(Event _event) { ... }
};

then

struct MyClass : Event_dispatcher
{
    // accessible as public
    // void add_listener(Listener _listener);
    // void remove_listener(Listener _listener);

    // only usage from within the class
    //void post_event(Event _event);
};

This approach relies on delegation and friendship.

You can make all Event_dispatcher member functions private and grand friendship to individual classes that would access those member functions instead:

class Event_dispatcher {
   friend class Listener_manager;
   friend class Event_poster;

   // all private
   void add_listener(Listener _listener) { /* */ }
   void remove_listener(Listener _listener) { /* */ }
   void post_event(Event _event) { /* */ }

   Listener_collection listeners;
};

Then, define Listener_manager and Event_poster classes that provides access to add_listener() / remove_listener() and post_event(), respectively. They both rely on delegation:

struct Listener_manager {
   Listener_manager(Event_dispatcher& ed): dispatcher(ed) {}   
   void add_listener(Listener _listener) {
      dispatcher.add_listener(_listener);
   }
   void remove_listener(Listener _listener) {
      dispatcher.remove_listener(_listener);
   }
private:
   Event_dispatcher& dispatcher;
};

struct Event_poster {
   Event_poster(Event_dispatcher& ed): dispatcher(ed) {}
   void post_event(Event _event) { dispatcher.post_event(_event); }
private:
   Event_dispatcher& dispatcher;
};

In your class, declare objects of these classes as data members. You can control the accessibilty of each member individually:

struct MyClass {
public:
   MyClass(): listener(dispatcher), poster(dispatcher) {}
private:
   Event_dispatcher dispatcher;
public: // accessible from the outside
   Listener_manager listener_manager; // add_listener()/remove_listener()
private:
   Event_poster event_poster; // post_event()
};

You could call add_listener() and remove_listener() as:

MyClass obj;

obj.listener.add_listener(listener);
obj.listener.remove_listener(listener);

Since the member event_poster is private, its member function post_event() can only be accessed inside MyClass.


Using CRTP

Instead, you can keep the original accessibility of the members of your Event_dispatcher class, and define the following class template, Accessor<>, which will handle the delegating part:

template<typename T>
class Accessor {
public:
   void add_listener(Listener _listener) {
      static_cast<T&>(*this).event_dispatcher.add_listener(_listener);
   }

   void remove_listener(Listener _listener) {
      static_cast<T&>(*this).event_dispatcher.remove_listener(_listener);
   }
};

Then, publicly inherit from this class template instantiated on the defining class itself (i.e., CRTP):

struct MyClass: Accessor<MyClass> {
   friend class Accessor<MyClass>;

private:
   Event_dispatcher event_dispatcher;
};

You still need to grant friendship to the base class so that it can access the private data member event_dispatcher – and this member must have this name.

This way, you can access Event_dispatcher::add_listener() and Event_dispatcher::remove_listener() in a more concise way:

MyClass obj;   

obj.add_listener(listener);
obj.remove_listener(listener);

I believe what you are looking for is friend classes:

cplusplus.com has a good page on this

TLDR;
When you specify class B as a friend of class A, class B has full access to the private functions of class A.

Try this:

class Outer_Class {
    public:
        Event_dispatcher my_event_dispatcher;
        // whatever else is public
    
    private:
        void example_func();
};

class Event_dispatcher {
    friend class Outer_Class;
    private:
        Listener_collection listeners;
        void post_event(Event _event) { ... }

    public:
        void add_listener(Listener _listener) { ... }
        void remove_listener(Listener _listener) { ... }
};

void Outer_Class::example_func(){
   // In here, you can use the functions from the class that made it a friend
   my_event_dispatcher.post_event(my_event);
}
Related