• Advertisement
Sign in to follow this  

Non-Member Functions Improve Encapsulation?

This topic is 1085 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Specifically when if comes to C++, could someone please explain to me why creating non-member functions is said to increase encapsulation for a class?

 

I kind of don't understand that, if I have a non-member non-friend function then anything I can manipulate for that class has to be in the public sector. And the way I see it, which I could be very very wrong here, is since that everything is already out in the open (public) there is no encapsulation of that class

 

 

Share this post


Link to post
Share on other sites
Advertisement

Specifically when if comes to C++, could someone please explain to me why creating non-member functions is said to increase encapsulation for a class?

 

I kind of don't understand that, if I have a non-member non-friend function then anything I can manipulate for that class has to be in the public sector. And the way I see it, which I could be very very wrong here, is since that everything is already out in the open (public) there is no encapsulation of that class

 

It can increase encapsulation. That doesn't mean it will - it depends on how you do it. :)

 

The case where it will increase encapsulation is where the only things the world knows about your class is that it exists and that the non-member functions exist. Having non-member functions as your public interface means that users of your class don't need to know the definition of your class to use it. Sort of like how C's FILE API works - you're not supposed to actually know what the definition of a FILE structure is, and in fact most of the time you don't, so your only access to this structure is through the functions that manipulate it.

Share this post


Link to post
Share on other sites

The case where it will increase encapsulation is where the only things the world knows about your class is that it exists and that the non-member functions exist. Having non-member functions as your public interface means that users of your class don't need to know the definition of your class to use it. Sort of like how C's FILE API works - you're not supposed to actually know what the definition of a FILE structure is, and in fact most of the time you don't, so your only access to this structure is through the functions that manipulate it.

I don't buy that definition. C's FILE API wouldn't be any less/more encapsulated if you converted all the functions to be member functions, and a C++ class using the PIMPL idiom hides just as much about its implementation as does hiding the class definition and providing non-member functions for the entire API...

 

The reason we say that using non-member functions improves encapsulation is that a non-member, non-friend function, by its very definition, cannot reduce encapsulation. No matter how many additional non-member functions you add, the degree of encapsulation remains defined solely by the member functions and public data members of the class in question.

Share this post


Link to post
Share on other sites

Specifically when if comes to C++, could someone please explain to me why creating non-member functions is said to increase encapsulation for a class?


For the same reason that we don't just make one huge monolithic Game class and then shove everything in there.

Your classes don't _need_ every operation to be a member. Most operations have no need to know about the internals.

Just look at the STL. If you have std::find, why would you need a .find() member on std::vector? Or .sort()? etc.

Algorithms and operations can operate on an object without knowing anything about the object's internals. The extensions of this is that operations can be coded generically such that they operate on many different types of objects based on a basic contract of interface.

Encapsulation means that your object should only expose the things needed to actually use its implementation. Sorting a vector is a very common operation but there's nothing about a vector that requires sort to be a member; the members of vector are (mostly) the fundamental operations necessary to manipulate a vector in the most efficient way possible. Any other algorithm or operation can be written externally, including find, sort, for_each, etc.

Part of encapsulation is reusability. Encapsulation by itself is pointless; it's not about protection or safety like some people seem to think, but rather about ensuring that changes to implementation don't affect outside users. If you make things member functions, then changing a class A for a class B in a function could easily break that function if B doesn't offer the same set of member functions, even if A and B both model the same fundamental concept (just in different ways). Again, compare std::vector with std::list, and note that you can use either interchangeably in many circumstances. The containers model a common interface and the free functions work with that interface, not with specific implementations.

Consider this simplified game case: You make a class called Player. Then you make a function Player::LevelUp(). The implementation has to update a member map<StatisticType, int> to apply the level-up mechanics. Then you realize that your statistic code is inefficient and swap out its implementation to use a vector<int>. Oops, now you need to change your LevelUp code. That's a bummer. Oh, also, if you decide to let NPCs level up at some point, you have to go rewrite that whole logic for the NPC class (or cut-n-paste it, or move it into a base class). Again, bummer. You go and do that and then realize you need a Spell Buff system that has to modify statistics the same way that LevelUp does, only you have to rewrite that code because it's bolted into LevelUp.

Now consider the fix: You make Player and give it an ModifyStatistic member. Then LevelUp is a free function. Now you can change your entire implementation of how statistics are tracked and LevelUp doesn't change, because it's just calling the public interface that hides (encapsulates!) all the implementation details. If you decide NPCs can level up, you just need to give them the much simpler ModifyStatistic and it just works with LevelUp. You can drop in a Spell Buff system without any modifications required to Player or NPC.

That's good encapsulation. You don't (can't!) make _everything_ a free function, but by preferring free functions you are more likely to find designs like the above.

You can certainly do things poorly, though. You might decide to give Player a function like std::vector<int> const& GetStatistics() for instance that now means you can't swap the implementation to use static array, nor to use a different allocator (since that's baked into the vector type), or whatever.

In the end, just remember your basic math. f(x)=x*2 works whether x is a scalar or a vector. It will also work with mathematical objects you likely haven't even heard of. x need only support the basic scale/multiply operation and it works with f. If instead we said that scalars can be doubled with a magic d(x) operation but then forget to also reiterate that vectors can be doubled, we might end up writing an expression like y=d(x) that ends up only working with scalars. By reducing our mathematical objects to our axiomatic operations, we can compose bigger and more complex operations on a wide class of mathematical objects interchangeably. That's what encapsulation (and by extension, use of free functions) is meant to do, just for C++ objects instead of in pure math.

Share this post


Link to post
Share on other sites

 

The case where it will increase encapsulation is where the only things the world knows about your class is that it exists and that the non-member functions exist. Having non-member functions as your public interface means that users of your class don't need to know the definition of your class to use it. Sort of like how C's FILE API works - you're not supposed to actually know what the definition of a FILE structure is, and in fact most of the time you don't, so your only access to this structure is through the functions that manipulate it.

I don't buy that definition. C's FILE API wouldn't be any less/more encapsulated if you converted all the functions to be member functions,

 

I was giving that as an example of what an opaque pointer API looks like, not saying that the FILE API in particular is any more encapsulated than anything else, just like a class that implements get/set methods is not necessarily any more encapsulated than anything else.

 

a C++ class using the PIMPL idiom hides just as much about its implementation as does hiding the class definition and providing non-member functions for the entire API...

 

Sure, using opaque pointers is encapsulating at about the same level as using the PIMPL idiom, and for the same reasons, but I'd say that going with the opaque pointer alone is a simpler design. With the opaque pointers you don't have a class that serves no purpose but to hide the implementation, which means less otherwise unnecessary delegation that needs to be maintained alongside the actual class. 

Share this post


Link to post
Share on other sites

I think your confusion is that what you now understand to be a synonym for 'encapsulation' is that you have a class, and inside it you have all its data and operations -- this satisfies a goal of encapsulating the class's internals from the outside world, and that's indeed a good thing.

 

This is "encapsulation 101" so to speak, which is the view that every class is an island unto itself.

 

 

But what you might notice in some of the member functions of your class, is that you might have several of them that are (or could be) purely implemented in terms of other members of your class's public interface (both member functions and member variables). When you have such a function that could be implemented in terms of existing public interfaces, but you instead make it a member, now that function has access to all the protected and private members that it doesn't need to do its job -- while this may seem innocuous at first, often the members and variables that are marked protected or private are marked so because they're either a shared utility (in which case, your member that could be implemented as a non-member doesn't need access), or they're involved in the internal book-keeping of the class (in which case, its dangerous to give that kind of power away to any member who doesn't need it.) In short, even though your non-member-candidate is comfortable being a member, this decreases encapsulation by granting it powers and access that it does not need to to its job.

 

This is encapsulation 201 -- Here, encapsulation is viewed to mean that any code construct, all the way down to single functions, should strive to have only the minimum powers and access that it needs to perform its job. This transforms your job as a class designer from someone who blindly puts all the related parts and functions into the same bag, to someone who considers all the parts and functions that are needed, and then chooses the minimum set of parts and functions that can be put in the bag for which the others can be implemented in terms of what's in the bag.

 

This may seem like a somewhat academic exercise, in which you are erecting walls to protect yourself from self-sabotage, and you are. But it is not academic -- as one example of a benefit, should you ever find a bug in your non-member and you know that your public members do not misuse the private and protected ones, you can instantly narrow your search, based on the fact that you know it cannot access those protected or private members.

 

You are right though, that just making everything a non-member does not achieve encapsulation if as a result you simply take parts of the interface that should remain private and make them public so that you can access them via non-members (as in Getters and Setters, or simply making more members public). That is the opposite of encapsulation. A well-encapsulated class strives for its members to represent the smallest reasonable (that is, it may not be the absolute minima, but a minima tempered with pragmatism) interface that provides for its entire interface and general uses to be provided for.

Edited by Ravyne

Share this post


Link to post
Share on other sites
If you can implement a method as a non member, non friend then it means that it has to use the existing public interface of the class.

A member or friend becomes one more point where the class internals might be touched, so one more potential update point if the internals change

I tend towards the view that if I would have to add new public interface just to implement one method, better use a member. If the logical existing public interface is enough, use a non member unless syntax requirements or convenience suggest otherwise.

But no clear right way here.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement