0

Is there a way to make a public member function private to certain classes? The reason I need to do this is because some classes must use something else in place of that public member function, else there will be serious bugs, but it is easy to forget and do that anyway. Example:

class Location {
    std::list<Person*> everyonePresent;
public:
    const std::list<Person*>& getEveryonePresent() const {return everyonePresent;}
};

class DoSomething {
    Person& person;
    std::list<Person*> everyoneVisible;
public:
    DoSomething(Person& p) : person(p), everyoneVisible(p.getLocation().getEveryonePresent()) {
       // everyoneVisible now removes those who are hiding.
   }
   void execute() {
       // ....
       ApproachSomeone(person, everyoneVisible).execute();
       // ...
   }
};

Now ApproachSomeone::execute() may accidentally use person.getLocation().getEveryonePresent() when it is not supposed to. But Location::getEveryonePresent() needs to be public because it is used in so many places, but ApproachSomeone is one of the few classes that must never use it. ApproachSomeone is a class because it is used in many other places (command pattern).

prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • 8
    You can't really do that, but you can do the opposite: make the function `private` (or `protected`) by default, and then declare other functions / classes as `friend` so that they may use that function even though it's private. – chris_se Jun 16 '23 at 14:01
  • Unfortunately, there's nothing in C++ like that, C++ does not work this way. You'll need to figure out some alternative design of all your classes that accomplishes the same results. – Sam Varshavchik Jun 16 '23 at 14:04
  • 1
    I would go for chris_se advice plus [client-attorney pattern](https://stackoverflow.com/questions/3217390/clean-c-granular-friend-equivalent-answer-attorney-client-idiom) in order to have a fine-grain friendness. – Oersted Jun 16 '23 at 14:05
  • 5
    The design of your class is seriously broken if some other classes (or functions) can introduce "serious bugs" by calling one of its `public` member functions. Generally speaking, all `public` member functions of a class should be able to rely on some set of preconditions being true and they must, in turn, ensure that some set of post-conditions (e.g. consistency of object state) is always met. That said, one option is to make the offending member function `private`, and declare the classes that need it to be "public" (and will call it without causing "serious bugs") as `friend`s. – Peter Jun 16 '23 at 14:25
  • 2
    I suggest you post the a [mre] that shows what the bugs would be, and what they should use instead. I smell an [XY Problem](https://meta.stackexchange.com/a/66378) – Superlokkus Jun 16 '23 at 14:35
  • I think it is more the other way around, you can make private functions public to some other classes. The friend keyword can help but I think is not fine grained enough (it makes all private details immediately accessible). And I like a bit more control (see answer) – Pepijn Kramer Jun 16 '23 at 14:46
  • @ Superlokkus Example given now. – prestokeys Jun 16 '23 at 14:55

1 Answers1

3

There is a way to model access on a per function basis if you are willing to do some dependency injection, or willing to do an explicit cast of a class to an abstract base class to gain access to private member functions.

I have used these approaches in cases where a class would need more then one "friend", but those friends should have distinct access to various member functions. It is still easier to do the correct thing, but not impossible to get "private" access. It is a good compromise and code can be checked on who gets access to what (so it is possible to detect misuse in code reviews too).

Live demo here: https://onlinegdb.com/TFVGm-HVW

First, we make two interfaces for per-class access to a class A:

#include <iostream>

class interface_for_class_b
{
public:
    virtual void method_for_class_b() = 0;
};

class interface_for_class_c
{
public:
    virtual void method_for_class_c() = 0;
};

Then, make a class A which inherits from both, but keeps the implementation private. This hides the member functions from everyone.

class A :
    public interface_for_class_b,
    public interface_for_class_c
{
public:
    void some_public_method()
    {
        std::cout << "A::some_public_method()\n";
    }

private:
    void method_for_class_b() override 
    {
        std::cout << "A::method_for_class_b()\n";
    };

    void method_for_class_c() override 
    {
        std::cout << "A::method_for_class_c()\n";
    };
};

Then, use dependency injection:

  • class B receives interface_for_class_b
  • class C receives interface_for_class_c
class B
{
public:
    explicit B(interface_for_class_b& a) :
        m_interface_to_a{ &a } { }

    void some_method()
    {
        std::cout << "B::some_method()\n";
        // B can access "private" function through the explicit interface.
        m_interface_to_a->method_for_class_b();
    }

private:
    interface_for_class_b* m_interface_to_a;
};

class C
{
public:
    explicit C(interface_for_class_c& c) :
        m_interface_to_a{ &c } { }

    void some_method()
    {
        std::cout << "C::some_method();\n";
        m_interface_to_a->method_for_class_c();
    }
    
private:
    interface_for_class_c* m_interface_to_a;
};

Here is how we use these classes:

int main()
{
    A a;
    B b{ a }; // inject dependency to give b access to "private"functions
    C c{ a };

    a.some_public_method();
    // a.method_for_class_b();
    // is not accessible, it is private
    b.some_method();
    c.some_method();

    // if you don't want to or cannot do dependency injection
    // there is an alternative way to get access to the specific methods
    // this explicit step says you want detailed access to class a
    // so it is also self-documenting.
    interface_for_class_b& itf{ a };
    itf.method_for_class_b();

    return 0;
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19