Abstract (tl;dr)
Please read the full question, this is awfully simplified:
How can unix file permission style restrictions be applied to inter-type data/control flows, allowing fine-grained access to some class-members for some groups of classes?
Background information
If you think about unix file systems and permissions, there is a diverse way of encoding file-access privileges of users (especially if you also consider FACL). For example, if a directory contains 3 files, they could belong to several users, and different other users may have restricted permissions:
-rwxr-xr-- jean-luc staff engage.sh
-rw-r----- william crew roster.txt
-rw------- beverly beverly patients.txt
Core idea
As you can see, depending on the groups a particular user is in, different access levels are allowed. For example, crew
members are allowed to read roster.txt
, which belongs to william
, but guests who presumably do not belong to crew
cannot. More importantly, the group crew
can contain many people.
So I was thinking that there is some similarity to access permissions inside object oriented languages like C++ if you think of types (classes) as users. Although a function can only be executed, but not read, the rwx
flags represent meaningful descriptions for class members. A data member can be read (r
) and written (w
) to, perhaps via accessors, while member functions may be executed (x
) or not.
However, in C++ and other object oriented languages (I know of), this is more or less an all or nothing thing, if we leave out inheritance for a second; If class William
makes his member Txt roster;
public, everybody will see it. If he makes it private, nobody except himself will see it. He may add one or more friends, friend JeanLuc;
but then they will see all his private members (the equivalent of granting user:jean-luc:rwx
to all his files, in FACL lingo).
This is entirely orthogonal to inheritance — JeanLuc
and William
are not part of the same hierarchy, they are not related.
So the main idea would be to allow group-based access restrictions, as a generalisation of private/public. Allowing finer grained inter-class access to member functions and member data.
I believe this idiom could help maintainability/readability, as it adds additional facets to restrict interaction permissions.
As with operating systems, where this adds an important layer of security to the system, the same familiar pattern could add safety to a C++ project.
Thoughts about representation in C++
However, I'm at a loss of thinking of a good way to represent this. You could decompose William
objects into several objects of subtypes: William_Crew
, William_William
and so forth, representing the respective groups. This seems to be horribly ugly. Another idea could be dedicated types with forwarder functions, representing the individual groups, like this:
class Crew { // group class
// in this group are:
friend JeanLuc;
friend Geordi;
friend Beverly;
// ...
static Txt getRoster(William*);
};
class William {
friend Crew; // Problem: Crew has full access (rwx)
Txt roster;
};
But each group would have to be tailored to a particular class to be used with, which would seem to be massively redundant, if the group is used by several users/classes.
Question
The approaches I provided are not great (to put it mildly), and I'm sure they wouldn't work as intended. I'm not sure if this is a novel/stupid/well-known idea, but I wonder how you could implement this with the features provided by the C++ language. Are there objective arguments why this would or would not be useful/helpful?
Best Answer
Rephrasing the problem a bit:
MySpecialObject
JLP
wants to call the methods ofMySpecialObject
BEV
wants to read the public data ofMySpecialObject
BEV
shouldn't be able to call member functionsThis should be as type-safe as possible.
A solution I see involves wrappers :)
MySpecialObject
as you would normally, ignoring this extra requirementMySpecialObject_Read
andMySpecialObject_Execute
.shared_ptr
ofMySpecialObject
MySpecialObject
,MySpecialObject_Read
andMySpecialObject_Execute
classes each have (as needed) a "convert to ..." method. This method returns an appropriate wrapper over the underlyingMySpecialObject
This solution provides a type-safe accessor with the desired limitations.
Each client class can choose what kind of access it needs. (And since you write these classes, you know what access you need.) If that is not acceptable, you could add factories that only create wrappers with certain limitations depending on a "token". That token might be the RTTI information of the calling class instance, although this could be abused.
Does this solve the original problem?
EDIT:
Remember that since you are the programmer you can instantiate an
A
whenever you want. By adding your class to afriend
list or whatever...What this solution provides is a clearer interface. Removing the explicit conversions probably makes it safer but less flexible. But, since they are explicit, you can easily look for them in the code and treat them as signs your architecture has a flaw somewhere.
Specifically, you can have code like this:
Here there is an explicit conversion between an execute wrapper and a write wrapper, but you can decide on this based on your specific requirements.
But, with little effort (knowing which conversions are valid), just by looking at the call locations you can see that (with confidence!):
logger
will not change the state of yourA
view
will not call methods on yourA
(other than setters)This is true even if those particular method calls end up calling hundreds of other methods, more than you'd like to examine by hand.
At the cost of a few thin wrappers you gain the ability to see at a glance what a particular function call will do with the parameters you send. This may help a lot during debugging by helping you eliminate some branches from your investigation and, in general, would help people trying to understand the program.
I couldn't really find other reasons for using this ACL idea, at least ones where the costs don't outweigh the benefits. However, it does seem more intuitive than the visitor solution mentioned in the other answer.