Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

The A::Restricted Idiom

5.00/5 (1 vote)
25 Feb 2014CPOL4 min read 4.8K  
Fine grained access control to private members of a class


Fine grained access control to private members of a class

Sometimes I wish I could control the access to a class in a finer way.
Usually you have these tools in your arsenal:

  • private
  • protected
  • public
  • friend

Private is fine, since most of the stuff in a class should be hidden from all, in order to maximize encapsulation.

Protected parts can be accessed by anybody who derives from your class. This opens your class too much, i.e., to every deriving class. Making these protected parts private later on can be impossible due to the number of deriving classes or due to not knowing who may derive from you (e.g. if you are a library). This can considerably hinder refactoring efforts.

Public parts have the same problems as the protected parts and more. The whole world can see your public parts. 

Making a non-related entity a friend of your class also exposes too much. The friend can access every part of your class even if it does not need to. 

The bottom line is, if you open up your class with protected or public then the encapsulation of your class is hurt badly. Also, making non-members as friends is almost always unnecessarily generous.

We would need better support in the core language for more specific control over who can access and what. Something like this:

C++
// WARNING! This is NOT C++! The "public(...)" is fictional.

class A {
public(class B, class D): // Private for all but public for B and D.
  void f();
private: // Private for all.
  int a;
};

class B : public A {
   void h() { f(); } // OK! A::f() is public for B.
};

class C : public A {
   void h() { f(); } // Error! A::f() is private for C.
};

class D {
   void h(A& a) { a.f(); } // OK! A::f() is public for D.
};

In the above code, A::f() is marked as public only for classes B and D. For everybody else, it is private (default access for class members).
Unfortunately, the public access modifier in C++ does not support this syntax and semantics.

Luckily, there is a workaround to emulate this kind of behavior. Here is one way to do this:

C++
// a.hpp
#pragma once

class A {
private:
    void f();
    virtual void g();
    double d;

public:
    A();

    class Restricted {
    private:
        A& parent;
        Restricted(A& p);
        // Proxy functions
        void f();
        void g();
        // Friends of Restricted
        friend class A;
        friend class B;
        friend class D;
    };
    Restricted restricted;
};
C++
// a.cpp
#include "a.hpp"
#include

A::A() : restricted{*this} { }
void A::f() { std::cout << "A::f()" << std::endl; }
void A::g() { std::cout << "A::g()" << std::endl; }

A::Restricted::Restricted(A& p) : parent{p} { }
void A::Restricted::f() { parent.f(); }
void A::Restricted::g() { parent.g(); }

Class A has some private parts, f, g and d. From these parts, we want to expose only the functions and we want to strictly control who can access them.

For this, we define an inner Restricted class. In Restricted, everything is private. We open it up only for selected entities; here A, B and D. We create proxy functions whose task is to forward the call to those parts in A that we want to make accessible to the friends of Restricted. The Restricted class has a reference to the outer A object to which it forwards the calls.

The friends of Restricted can access Restricted::parent, but this is not a problem at all. The parent is a reference, so it cannot be changed to point to some other A after construction. Also, only the public parts of A can be access through parent. The encapsulation of A is not weakened.

The friends of Restricted can access Restricted::Restricted(A&) and construct Restricted objects, but this is not a problem either. In the worst case, this can result in multiple Restricted objects referencing the same A object. Again, the encapsulation of A is not weakened because through these Restricted::parent references, only the public parts of A can be accessed. Also, through these Restricted objects, only the selected private parts of A can be accessed (here A::f() via A::Restricted::f() and A::g() via A::Restricted::g()).

In class A, everything is private and only the very minimum is made public. A::A() is public because we want to allow anybody to create A objects. A::restricted is public because otherwise the friends (e.g. B) specified inside Restricted cannot access it.

The examples below show how class A can be used. Access control to A::Restricted is managed strictly by A via the friends of A::Restricted. So, nobody else can gain access without the “permission” of A.

We create a class B deriving from A. B is a friend of A::Restricted. This gives B access to the restricted parts of A.

C++
// b.hpp
#pragma once
#include "a.hpp"
class B : public A {
public:
    void h();
private:
    virtual void g() override;
};
C++
// b.cpp
#include "b.hpp"
#include

void B::h() {
    std::cout << "B::h() enter" << std::endl;
    // ++d;         // Error! A::d is private.
    // f();         // Error! A::f() is private.
    g();            // OK! B::g() is accessible here.
    restricted.f(); // OK! A::Restricted::f() is public here.
    restricted.g(); // OK! A::Restricted::g() is public here.
    std::cout << "B::h() exit" << std::endl;
}

void B::g() { std::cout << "B::g()" << std::endl; }

We create a class C deriving from A, but we do not give it access to the restricted parts of A.

C++
// c.hpp
#pragma once
#include "a.hpp"
class C : public A {
public:
    void h();
};
C++
// c.cpp
#include "c.hpp"
void C::h() {
    // f();             // Error! A::f() is private.
    // g();             // Error! A::g() is private.
    // restricted.f();  // Error! A::Restricted::f() is private.
    // restricted.g();  // Error! A::Restricted::g() is private.
}

We create a class D that is not related to A. Yet, it can access the restricted parts of A because we explicitly allow it.

C++
// d.hpp
#pragma once
class D {
public:
    void h(class A&);
};
C++
// d.cpp
#include "d.hpp"
#include "a.hpp"
void D::h(A& a) {
    // a.f();            // Error! A::f() is private.
    // a.g();            // Error! A::g() is private.
    a.restricted.f(); // OK! A::Restricted::f() is public here.
    a.restricted.g(); // OK! A::Restricted::g() is public here.
}
C++
// main.cpp
#include "b.hpp"
#include "c.hpp"
#include "d.hpp"
int main() {
    auto b = B{};    b.h();
    // b.f();            // Error! A::f() is private.
    // b.g();            // Error! B::g() is private.
    // b.restricted.f(); // Error! A::Restricted::f() is private.
    auto c = C{};    c.h();
    auto d = D{};    d.h(b);
    return 0;
}

In this example, only B and D can access A::f() and A::g(), but only through A::Restricted. Nobody else can. A::d remains private for everybody. And this I call the A::Restricted idiom.
Here is the output of this example program:

B::h() enter
B::g()
A::f()
B::g()
B::h() exit
A::f()
B::g()

This idiom is a variation of the Attorney-Client Idiom. I prefer this variant (i.e. A::Restricted) because it provides a more convenient and more intuitive syntax for accessing the restricted parts. This is achieved by the automatic wiring between class A and class Restricted.

References


License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)