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
To directly answer the question you asked, I'd say the short answer is no: it's not necessarily worthwhile writing a wrapper for every member function, just to give a uniform interface.
That said, it can be worthwhile to do so if you have something that corresponds closely to a concept, that's defined entirely in terms of free functions, and by providing free-function wrappers, your class can model that concept. In this case, having those wrappers lets you use objects of that class with some set of templates that wouldn't otherwise work with them. The question at that point is whether those templates are likely to be useful with objects of this particular class. Even if the class happens to support the syntax necessary to model the concept, the operation(s) provided by those templates may not be useful with it--in which case, it's obviously pretty pointless.
At least in my view, providing wrappers so
x.f(y)
can be called likef(x,y)
, just so the programmer doesn't need to pay attention to whetherf
is a free function or a member function is generally misplaced. Yes, in a completely ideal world where time wasn't constrained at all, it might be worth considering. Likewise, in a few cases a class might be used so widely and heavily that it's worth considering putting in the extra time to write the wrapper in the hope of its paying off in the long run by making everybody else's lives easier.Unfortunately, I don't think either of these applies very often. Most of us have way too large of a backlog of problems that really need fixing to give it serious consideration, at least until we have solid, objective evidence that the investment will pay off, and fairly soon at that. Doing it just to fulfill a principle without a fairly solid assurance of real benefits would be difficult to justify (at best).