CodeProject
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:
class A {
public(class B, class D): void f();
private: int a;
};
class B : public A {
void h() { f(); } };
class C : public A {
void h() { f(); } };
class D {
void h(A& a) { a.f(); } };
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:
#pragma once
class A {
private:
void f();
virtual void g();
double d;
public:
A();
class Restricted {
private:
A& parent;
Restricted(A& p);
void f();
void g();
friend class A;
friend class B;
friend class D;
};
Restricted restricted;
};
#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
.
#pragma once
#include "a.hpp"
class B : public A {
public:
void h();
private:
virtual void g() override;
};
#include "b.hpp"
#include
void B::h() {
std::cout << "B::h() enter" << std::endl;
g(); restricted.f(); restricted.g(); 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
.
#pragma once
#include "a.hpp"
class C : public A {
public:
void h();
};
#include "c.hpp"
void C::h() {
}
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.
#pragma once
class D {
public:
void h(class A&);
};
#include "d.hpp"
#include "a.hpp"
void D::h(A& a) {
a.restricted.f(); a.restricted.g(); }
#include "b.hpp"
#include "c.hpp"
#include "d.hpp"
int main() {
auto b = B{}; b.h();
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