argcv
argcv

Reputation: 51

Is there a way in C++ to restrict a function of a given class to another class only(without using inheritance, friend)?

I want to design a class having a function which should be restricted to be called from another class only. Specifically, in the given code

class Club
{
    int id;
    string name;
    vector<string> members;
    int generateId() 
    { 
        static int i=1;
        return i++;
    }
public:
    Club(string name) { this->name = name; this->id = generateId(); }  
    void registerMember(string memberName) { members.push_back(memberName); }
    int getId() { return id; }
};

class Application
{
    vector<Club> clubs;
public:
    void registerClub(Club &club) { clubs.push_back(club); }
    void addMemberToClub(int clubId, string memberName)
    {
        for(Club club: clubs)
        {
            if(clubId == club.getId())
                club.registerMember(memberName);
        }
    }
};

An user(public user) can create an object of the class Club and register using the function registerMember() since it's public. I want the user to register via an object of the class Application, using the addMemberToClub() function only. If the user goes by the former way mentioned, I can't keep track of the user. Is there a way to enforce the latter?

  1. I don't want to use the access modifier protected since inheritance has no meaning here.
  2. I don't want to use the friend keyword, since it's considered bad practice.

Upvotes: 1

Views: 1289

Answers (2)

Ben Voigt
Ben Voigt

Reputation: 283733

Here is a "lock-and-key" way to permit another class (and only that class) or even a single function in another class to access just one member function, unlike friend which exposes all private members at the same time:

#include <iostream>

class Key;

class Locked
{
    static const char* const Greeting;
public:
    static Key secretive();
    static void attacker();
};

struct Admin
{
    void doit();
};

class Key
{
    ~Key() = default;
    //friend class Admin;
    friend void Admin::doit();
    friend Key Locked::secretive();
};

void Admin::doit()
{
    Locked::secretive();
    std::cout << Locked::Greeting; // compile error
}

constexpr const char* Locked::Greeting = "Hello!\n";

Key Locked::secretive()
{
    std::cout << Greeting;
    return Key();
}

void Locked::attacker()
{
    std::cout << Locked::Greeting; // ok, it's just private
    Locked::secretive(); // compile error, it's locked down tight
}

int main()
{
    Admin a;
    a.doit();
    std::cout << Locked::Greeting; // compile error
    Locked::secretive(); // compile error
}

It also works around the "which class is declared first?" problem that prevents two classes from mutually friending individual member functions of each other, because the restricted operation needs to follow only a forward declaration of the key type; the full definition of the other type can (and in this example does) appear above the key definition, allowing individual members to be named in the key type's friend directive.

Note that in this solution the "obvious" fact that other members of the same class can access the locked function is NOT true. The compiler prevents Locked::attacker() from calling Locked::secretive().

Note also that I've used static in this example to minimize the number of objects I had to create, but the approach works just fine for non-static member functions too.


A potentially MUCH easier way to restrict what part of the program can call your protected function is with a simple flag:

class Application
{
    static bool addingMember = 0;
public:
    static bool isRegistrationOk() { return addingMember; }
    void registerClub(Club &club) { clubs.push_back(club); }
    void addMemberToClub(int clubId, string memberName)
    {
        addingMember = true;
        for(Club club: clubs)
        {
            if(clubId == club.getId())
                club.registerMember(memberName);
        }
        addingMember = false;
    }
};

void Club::registerMember(string memberName)
{
    assert(Application::isRegistrationOk());
    members.push_back(memberName);
}

Much easier to grok, but it's a runtime check not compile-time, and requires additional work to be made thread-safe. But it accomplishes the goal with no usage of friend or inheritance.

Upvotes: 2

cigien
cigien

Reputation: 60238

friend is an appropriate mechanism to use in this case. Make registerMember private in Club, and Club can grant friendship to Application:

class Club
{
    // ...
    void registerMember(string memberName) { members.push_back(memberName); }
public:
    // ...
    friend class Application;
};

Now only Application can call registerMember, and Club as well, of course.

Here's a demo.

Upvotes: 1

Related Questions