re your #2: Actually, making functions non-members often increases encapsulation, as Scott Meyers observed more than a decade ago.
Anyway, what you describe sounds much like C#'s extension methods to me. They are good to soothe the minds of those that are frightened when they see totally free functions. :)
Once you've braved the STL (which, BTW, is not the standard library, but only that part of it which comes from the original STL) where almost all functions are so free that they aren't even real functions, but function templates, you will no longer need those.
In short: Don't try to bend a language to adapt to your mindset. Instead enhance your mindset to embrace the language's philosophy. You will emerge a bit taller from doing so.
Rephrasing the problem a bit:
- I have an instance of
MySpecialObject
- an instance of
JLP
wants to call the methods of MySpecialObject
- an instance of
BEV
wants to read the public data of MySpecialObject
- the instance of JLP shouldn't be able to read public data and the
BEV
shouldn't be able to call member functions
This should be as type-safe as possible.
A solution I see involves wrappers :)
- you write the code for
MySpecialObject
as you would normally, ignoring this extra requirement
- you write a wrapper for each aspect of your class you wish to restrict. Say,
MySpecialObject_Read
and MySpecialObject_Execute
.
- these wrappers forward the requests (method calls, getter/setters) to an underlying
shared_ptr
of MySpecialObject
- your
MySpecialObject
, MySpecialObject_Read
and MySpecialObject_Execute
classes each have (as needed) a "convert to ..." method. This method returns an appropriate wrapper over the underlying MySpecialObject
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 a friend
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:
shared_ptr<A> * a = new A(parameters);
A_read aRead(a);
A_execute aExec(a);
A_write aWrite(aExec);
logger->Log(aRead);
view->SetUserData(aWrite);
controller->Execute(aExec);
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!):
- the
logger
will not change the state of your A
- the
view
will not call methods on your A
(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.
Best Answer
Use the following rules:
For any non-static method of a class which a) does not access private fields directly and b) only calls public methods, move the method to a static helper class and turn it into an extension method.
Any public static method can also be moved to a helper class.
By convention, a helper is a new static class with the same name as the original class but with the word Helper appended to it.
Example:
ORIGINAL CODE:
PROPOSED TRANSOFORMATION: