When should I prefer non-member non-friend functions to member functions?
Asked Answered
V

4

18

Meyers mentioned in his book Effective C++ that in certain scenarios non-member non-friend functions are better encapsulated than member functions.

Example:

// Web browser allows to clear something
class WebBrowser {
public:
  ...
  void clearCache();
  void clearHistory();
  void removeCookies();
  ...
};

Many users will want to perform all these actions together, so WebBrowser might also offer a function to do just that:

class WebBrowser {
public:
  ...
  void clearEverything();  // calls clearCache, clearHistory, removeCookies
  ...
};

The other way is to define a non-member non-friend function.

void clearBrowser(WebBrowser& wb)
{
  wb.clearCache();
  wb.clearHistory();
  wb.removeCookies();
}

The non-member function is better because "it doesn't increase the number of functions that can access the private parts of the class.", thus leading to better encapsulation.

Functions like clearBrowser are convenience functions because they can't offer any functionality a WebBrowser client couldn't already get in some other way. For example, if clearBrowser didn't exist, clients could just call clearCache, clearHistory, and removeCookies themselves.

To me, the example of convenience functions is reasonable. But is there any example other than convenience function when non-member version excels?

More generally, what are the rules of when to use which?

Velours answered 19/10, 2011 at 12:29 Comment(8)
I think there is more religion than actual objective rules to that topic.Mahound
Any sort of algorithms benefit from being free rather than members, since you can reuse them for a wider range of objects. An example of this is the free begin()/end() functions that were added to the new standard (which allow you to iterate static arrays as well as containers).Wesson
@PlasmaHH: I don't think it is more like religion. The rationale for making some functions non-member and non-friend is quite reasonable.Shults
@Nawaz: I still think there is more religion than the rules in your answer. I never said there isn't anything objective, just that in the majority of cases its a religious thing. Just have a look at how std::string and the mentioned rules collide.Mahound
@PlasmaHH: That doesn't makes sense to me, because Scott Meyer didn't design std::string class. Also, there are too many things in the Standard library which aren't reasonable, but they are there for a reason, and the reason is called compatibility.Shults
@Nawaz: compatibility to what?Mahound
@PlasmaHH: the string class existed (more or less) prior to the Standard design, you will notice it is totally at odd with the rather modern design of the STL part of the Standard Library. One key flaw is the redundancy between index-based and iterator-based methods as well as the custom find set that collide with <algorithm> find flavors. You may also note that IOStreams is itself apart from the others, once again it's historical and was kept for compatibility reasons. See Sutter's take on the interface at gotw.ca/gotw/084.htmZerlina
@PlasmaHH: The std::string existed much before Scott Meyer came up with these rules and lots of other recent idioms and techniques discovered by other experts. Having said that, now if the non-member and non-friend is a reasonable choice, the implementation of std::string would remain same, just to be compatible with the old code. That is what I think.Shults
S
22

More generally, what are the rules of when to use which?

Here is what Scott Meyer's rules are (source):

Scott has an interesting article in print which advocates that non-member non-friend functions improve encapsulation for classes. He uses the following algorithm to determine where a function f gets placed:

if (f needs to be virtual)
    make f a member function of C;
else if (f is operator>> or operator<<)
{
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
}
else if (f needs type conversions on its left-most argument)
{
   make f a non-member function;
   if (f needs access to non-public members of C)
      make f a friend of C;
}
else if (f can be implemented via C's public interface)
   make f a non-member function;
else
   make f a member function of C;

His definition of encapsulation involves the number of functions which are impacted when private data members are changed.

Which pretty much sums it all up, and it is quite reasonable as well, in my opinion.

Shults answered 19/10, 2011 at 12:41 Comment(5)
Can you explain plz, what does this mean? "f needs type conversions on its left-most argument" ? And "f can be implemented via C's public interface"?Gumwood
@GusevSlava: Let's assume you have defined a class called myvector<T> which is just like std::vector with public member-functions begin() and end() .. and now you wan to support a functionality to print all the elements in your vector. So would you add print() as member function, or as non-member function? As per Scott's guideline, print() should be non-member function, as it can be implemented in terms of begin() and end() of your vector class. So in this example: C is myvector, f is print() and C's public interface is begin() and end(). Hope that helps.Shults
I understand thanks! What about this: "f needs type conversions on its left-most argument" ?Gumwood
@GusevSlava: That is useful when you implement operators such as +. Let's say, you want to support 10 + a where a is an instance of your class which have a non-explicit single-parameter constructor that takes int. How would you implement the functionality that can be used to compute 10+a. Member function wont work in such case.Shults
Does it combine well with answers from here: #2315666 ? I understand that Scott Meyer recommends to make such functions as foo::create() as friend not static member function.Gumwood
F
3

I often choose to build utility methods outside of my classes when they are application specific.

The application is usually in a different context then the engines doing the work underneath. If we take you example of a web browser, the 3 clear methods belongs to the web engine as this is needed functionality that would be difficult to implement anywhere else, however, the ClearEverything() is definitely more application specific. In this instance your application might have a small dialog that has a clear all button to help the user be more efficient. Maybe this is not something another application re-using your web browser engine would want to do and therefor having it in the engine class would just be more clutter.

Another example is a in a mathematic libraries. Often it make sense to have more advanced functionality like mean value or standard derivation implemented as part of a mathematical class. However, if you have an application specific way to calculate some type of mean that is not the standard version, it should probably be outside of your class and part of a utility library specific to you application.

I have never been a big fan of strong hardcoded rules to implement things in one way or another, it’s often a matter of ideology and principles.

M.

Freshen answered 19/10, 2011 at 12:52 Comment(0)
B
1

Non-member functions are commonly used when the developer of a library wants to write binary operators that can be overloaded on either argument with a class type, since if you make them a member of the class you can only overload on the second argument (the first is implicitly an object of that class). The various arithmetic operators for complex are perhaps the definitive example for this.

In the example you cite, the motivation is of another kind: use the least coupled design that still allows you to do the job.

This means that while clearEverything could (and, to be frank, would be quite likely to) be made a member, we don't make it one because it does not technically have to be. This buys you two things:

  1. You don't have to accept the responsibility of having a clearEverything method in your public interface (once you ship with one, you 're married to it for life).
  2. The number of functions with access to the private members of the class is one lower, hence any changes in the future will be easier to perform and less likely to cause bugs.

That said, in this particular example I feel that the concept is being taken too far, and for such an "innocent" function I 'd gravitate towards making it a member. But the concept is sound, and in "real world" scenarios where things are not so simple it would make much more sense.

Byzantium answered 19/10, 2011 at 12:48 Comment(0)
C
0

Locality and allowing the class to provide 'enough' features while maintaining encapsulation are some things to consider.

If WebBrowser is reused in many places, the dependencies/clients may define multiple convenience functions. This keeps your classes (WebBrowser) lightweight and easy to manage.

The inverse would be that the WebBrowser ends up pleasing all clients, and just becomes some monolithic beast that is difficult to change.

Do you find the class is lacking functionality once it has been put to use in multiple scenarios? Do patterns emerge in your convenience functions? It's best (IMO) to defer formally extending the class's interface until patterns emerge and there is a good reason to add this functionality. A minimal class is easier to maintain, but you don't want redundant implementations all over the place because that pushes the maintenance burden onto your clients.

If your convenience functions are complex to implement, or there is a common case which can improve performance significantly (e.g. to empty a thread safe collection with one lock, rather than one element at a time with a lock each time), then you may also want to consider that case.

There will also be cases where you realize something is genuinely missing from the WebBrowser as you use it.

Carlos answered 19/10, 2011 at 13:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.