ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinIntuitive Multi-Paradigm Design:

Overload Journal #38 - Jul 2000 + Programming Topics   Author: Ian Bruntlett

Do I use a member function or a non-member function?

This question was posed in an article [1] by Scott Meyers. How you answer it depends on your perspective.

The best approach adopts a problem based perspective: What are you trying to achieve?

However, as a thought experiment, I'll take an implementation perspective. At first, I thought we were mixing two paradigms, Object Orientation (OO.) and Procedural (algorithms).

Different designers will produce different designs for the same problem. A key factor is the paradigm mix they prefer. Rephrasing the question makes it easier to answer:

"What are the design implications of member functions and non-member functions?"

The answer, ultimately depends more on your personal design style mix than the original problem. Here are two extreme examples:

If you are heavily into OO then you'll be using member functions everywhere. Even your algorithms will be wrapped up in classes and are likely to be passed around as objects. The original question becomes "which class do I make this a member function of?"

If you're heavily into procedural programming, there'll be plenty of highly-cohesive, weakly coupled functions manipulating a small set of built in types. The original question never gets asked.

Actually you are somewhere between those two artificial extremes. Everyone knows the C++ language supports multiple paradigms - procedural, modular, object-based, object-oriented and more. And language shapes thought. So, at some intuitive level, we're all doing multi-paradigm design and implementation. There comes a point where it is worth looking at what we're doing more formally. Which is a perfect excuse to introduce some formal terminology…

Multi-Paradigm Design

A paradigm is just a matter of perspective - a way of grouping similar things together and noting their differences. Some points of views are similar and overlap (object-based, object-oriented). As far as intuition is concerned, the differences do not matter.

When weaving paradigms together, I find it a helpful crutch to have artificially rigid definitions of the paradigms I'm using. The paradigms have to overlap in some way so they can be stitched together. Personally I think the different paradigms of C++ can be bundled together as families of paradigms (algorithms, user defined types, declarative).

When designing something, we make a careful study of the situation/business (domain analysis). Sometimes the business is so broad that its best split up into smaller areas. I prefer to split the business focus into sub-domains (e.g. forecasting, risk-analysis) and the technology into sub-systems (e.g. databases, GUIs).

When we split something up, we group things together into bunches (families) based on what they have in common (commonality analysis) and how the family members differ (variability analysis).

Table 2 (next page) describes an informal, intuitive approach to Multi-Paradigm design. At the end of this intuition-driven approach, there may be some functions that could be put in classes or could be stand-alone functions. This uncertainty shows that we will discover something later.

Once the topic/domain has been split into sub-domains and sub-systems, the choice of paradigm may be obvious to intuition.

While paradigm choice is important in selecting the tools we will use, it only brings us nearer to the answer of our question. Some email discussion with Kevlin Henney pointed out that "questions … where responsibility for behaviour is allocated .. the forces at play on this include extensibility, primitiveness, dependencies, and so on…you might want to look at …the Interface Principle".

"Does the function's behaviour belong inside the class?"

Applying design principles.

How about considering the Open Closed Principle (OCP)? That is, what we implement should be open for extension and closed to change.

Applying the OCP to the function, should the function extendable (applicable to other types)? If so then we should consider making it a template function.

According to the Interface Principle (IP) [4], regardless of whether the function is implemented as a member function or a freestanding function, the function is part of the class' interface. I will refer to the member functions as the class method interface.

So, taking the OCP and IP together, if we add a member function to the class we change it, if we write a freestanding function that mentions the class, we extend the class - without changing the class definition or using virtual functions! According to the OCP, we should provide things that are open to extension and closed to change.

For the class to be closed to change, we need to provide member functions that provide meaningful services so that non-member functions do not have to access private data.

We need to look at the function and the class regarding the principles of weak coupling and high cohesion (and consider granularity for good measure). A coupling issue arises if the function relies on private data. We then need to make a decision based on cohesion to either (1) make the function a member function or (2) write the function as a non-member function and write some additional member functions so that the private data is no longer an obstacle.

Polymorphism requirements don't affect our decision. If polymorphism is needed, the function should either be a virtual member function or a freestanding function that accepts a base class pointer/reference as a parameter.

Remember, though, whatever choice you make, it should take the application into account.

<colgroup> <col> <col></colgroup> <tbody> </tbody>
Member function implies Non-member function implies

Function is part of the class method interface.

Function is an essential part of the class and must
not be separated from it (i.e. needs access to
private/protected data).

(if virtual) - the function is a part of the class'
runtime polymorphism.

Responsibility for the function's behaviour lies with
the class.

Dependencies - the function prototype may require
additional headers to be included by the class' user.

Function is part of the class interface.

Function is an algorithm.

Function prototype declares which classes/types it can
be applied to.

Algorithm requirements implied by the function body.

(if template) - function is sufficiently generic that it
can be applied to a set of types/classes which implement
a set of facilities for the template function (note -
these facilities are implied by the function
implementation).

Can avoid additional dependencies - function prototype
can be stored in a different header to the class
definition.

Table 1. Design implications of member functions and non-member functions

Ambiguity implies the function is one of:

  • A future member function of

  • A class that does not exist yet.

  • A class we do not know enough about to decide.

  • A future algorithm that may get applied to different types one day using

  • Runtime polymorphism (inheritance class hierarchy)

  • Compile time polymorphism (template function).

Examples of mixing both:

  • A template that takes base class pointers as parameters.

  • A class hierarchy in which the member functions are implemented using different, smaller function templates.

<colgroup> <col></colgroup> <tbody> </tbody>
Think about the problem.

Break the original problem (application domain) into sub-domains & sub-systems.
Identify significant processes (algorithms) on smaller post-it notes.
Identify classes - families grouped by common structure and behaviour, family members distinguished by variability in
behaviour and/or additional structure and behaviour.
Entities (classes, objects), their responsibilities, behaviour and collaborations.
Relationships, Collaborations and Interfaces between objects.
Operations performed by objects (methods - member functions).
Identify algorithms - families grouped together by common name & purpose (overloading and/or template functions), family
members distinguished by variability in implementation, function signature (overloading), types (template functions).
Flow of information / objects.
Operations performed on objects (algorithms - non member functions).
Identify groups of objects (using database normalisation techniques).
Assign processes to CRC cards (as member functions), keep others to one side (non-member functions).
Assign algorithms to post it notes.
Play with the CRC cards and other post it notes.
Implement your entities as classes, aiming for a "thin method interface".
Implement your algorithms as functions that manipulate objects via their member functions.

Table 2. A development approach.

Acknowledgements

Thanks to Kevlin Henney and Phil Bass for their comments.

References & Further Reading.

[Meyers] How Non-Member Functions Improve Encapsulation (Scott Meyers, via www.aristeia.com)

[Stroustrup] The C++ Programming Language 3rd edition. (Bjarne Stroustrup). Chapter 2: Overview of paradigms and Part IV: Design Using C++.

[Coplien] Multi-Paradigm Design for C++ (James O. Coplien). Especially chapters on Solution domain analysis, Commonality, Variability.

[Sutter] Exceptional C++ (Herb Sutter)

Combining O.O. Design and Generic Programming ( Klaus Kreft & Angelika Langer, C++ Report March 1997, home.camelot.de/langer/Articles/OOPvsGP/Introduction.htm)

C++ Primer 3e (Lippman & Lajoie) chapters on Function templates, Overloaded functions.

Overload Journal #38 - Jul 2000 + Programming Topics