Prefer non-member functions

In C++ you can write a function as member function of a class or as non-member function outside of classes. But which of this both software designs is applicable? Which one do you should prefer? The answer is easy: It depends! Within this article I want to give you some guidelines which you may use to make decisions about this software design question. This will help you in your day by day business because as software developers we will write functions every day and therefore it’s a very fundamental question where we want to place this functions: inside or outside of a class.

To think about this question we will use an easy example. Let’s say we want to implement a database access. Typical functionalities of such a database access are connection management and create, read, update and delete values. The following example shows a basic implementation.

class DatabaseConnection;
class DatabaseValue;

namespace Database
{
  class DatabaseAccess
  {
  public:
    void Connect(std::string connectionSettings);
    void Disconnect();

    void InsertValue(std::string sqlQuery);
    DatabaseValue ReadValue(std::string sqlQuery);
    void UpdateValue(std::string sqlQuery);
    void DeleteValue(std::string sqlQuery);

  private:
    DatabaseConnection mConnection;
  };
}

I have implemented all functions as member functions inside of a class. Why did I choose this software design? It is also possible to write non-member functions instead.

namespace Database
{
  DatabaseConnection Connect(std::string connectionSettings);
  void Disconnect(DatabaseConnection connection);

  void InsertValue(DatabaseConnection connection, std::string sqlQuery);
  DatabaseValue ReadValue(DatabaseConnection connection, std::string sqlQuery);
  void UpdateValue(DatabaseConnection connection, std::string sqlQuery);
  void DeleteValue(DatabaseConnection connection, std::string sqlQuery);
}

As you can see, all functions share a common data member, the database connection object. If you use non-member functions you have to hand around this connection object, or even worse, you have to use a global variable. Therefore this use case is a perfect candidate for a class with member functions. The class will have its internal data and resource management which will be used by the member functions. And it will have the public interface with the member functions a client needs to implement database functionalities.

After implementation of this class you will use it within client. At this moment you may see that most of the clients will have a few database accesses only and most often read single values. Therefore we now have the need for a function which establish a database connection, read the value and closes the connection. You may add this function to the existing database class or write a non-member function. The following source code shows both implementations.

namespace Database
{
  class DatabaseAccess
  {
  public:
    void Connect(std::string connectionSettings);
    void Disconnect();

    void InsertValue(std::string sqlQuery);
    DatabaseValue ReadValue(std::string sqlQuery);
    void UpdateValue(std::string sqlQuery);
    void DeleteValue(std::string sqlQuery);

    // member function
    DatabaseValue ReadSingleValue(std::string connectionSettings, std::string sqlQuery)
    {
      DatabaseValue dbValue;
      DatabaseAccess dbAccess = DatabaseAccess();

      dbAccess.Connect(connectionSettings);
      dbValue = dbAccess.ReadValue(sqlQuery);
      dbAccess.Disconnect();

      return dbValue;
    }

  private:
    DatabaseConnection mDatabaseConnection;
  };

  // non-member function
  DatabaseValue ReadSingleValue(std::string connectionSettings, std::string sqlQuery)
  {
    DatabaseValue dbValue;
    DatabaseAccess dbAccess = DatabaseAccess();

    dbAccess.Connect(connectionSettings);
    dbValue = dbAccess.ReadValue(sqlQuery);
    dbAccess.Disconnect();

    return dbValue;
  }
}

What do you think is the better solution? Implement the method as member function or as non-member function?

I think this method should not be a member of the class. There are several reasons for my opinion. At first, the method adds a new concept to the class which does not fit into the existing interface. The existing interface is based on a connection which you have to establish and close and several database access functions you may use between opening and closing of the connection. The new function has a completely new functionality which is independent of the existing ones. It needs an own connection settings string and maybe it will result in errors if you already called the connect-function. So this new function does not fit into the existing interface and therefore makes the class harder to use for a client.

Another important aspect is the implementation of the function. As you can see we don’t use the class internal members within the function. This is a strong indication for the guess that the function should not part of the class.

Therefore I prefer to implement this method as non-member function. The method is strongly connected with the database access class because both belong to the same group of functionalities. But it is not part of the database access class itself because it does not extend the class functionality. It adds a new group of functionalities instead.

Non-member functions and classes which belong to the same topic can be grouped within the same namespace. Like in this example they all belong to the group of database specific functionalities and therefore I have grouped them together within the Database namespace.

Guidelines for non-member functions

After this comparison between member functions and non-member functions I want to think about some important aspects of non-member functions. At the beginning of this article I wrote a class and within the next code example I just take the class methods and put them outside of the class as non-member functions. But this is dangerous and create hard to maintain and error-prone code. That’s because these functions are non-member functions from a technical point of view but not from a logical point of view. They all belong together, can only be called in the right order and may have side effects or must hand around resources.

Therefore I want to talk about some guidelines you should take in mind whenever you write a non-member function. From my point of view a non-member function respect the following aspects:

  • Atomic
  • Stateless
  • Without side effects

A function is atomic if it can be executed without the need of other functions. Within the above source code with the database functions as non-members, the functions depend on each other. You have to call the Connect function first before you can use the Insert, Read, Update and Delete functions. And at the end you must not forget to call the Disconnect function. So these functions have a strong relationship. You cannot use o of the functions you always have to use the right combination of several functions.

If you have such an atomic function, it should be stateless. Sometimes stateless functions are also called service functions. It means that if you call the function with the same parameters several times it will always return the same result independent of the state of the application or the system where it is executed. For example you may try to break up the strong relationship between the database non-member functions and put the database connection into a global variable to remove the need for the data turn around. In this case the relationship between the functions will be reduced. But of course you are far away from atomic functions and add a global variable which results in some other issues. But if you do so your functions will become state-controlled. For example the Read-function has a SQL-query as input and return the database value. But their functionality is state-controlled now as it will check the state of the database connection, which is now stored within the global variable. If the connection is established the function will return the result of the SQL-query, otherwise it may throw an exception. The non-member function will now become a state-controlled non-service function.

Another very important aspect is the question whether the function has side effects or not. Maybe that’s the most important of the three aspects because if it is not fulfilled it can result in big issues and make the source code very hard to maintain and error-prone. To have a function without side-effects, the function should not change anything within your software system. So, for example, it should not write into a global variable, should not set some application property and it should not create any kind of output file. This may surprise you as we have implemented a database non-member function which will read one value from the database and you may now think what if we want to write one value? Is it bad style if we put this in a non-member function? Furthermore the last example that we don’t want to create some kind of output file may be surprising too as many of the non-member functions are implemented especially fur such helper or tool methods. You are right if you think about the functionality of this non-member function. But there are huge differences if we think about the two main possibilities how we can implement this functionality. For example if you have a data object and you want to serialize into a file in xml format. You may implement this within a non-member function. This will contravene the guideline that a non-member function shall not have any side effects because in this case we create a file and therefore change out system. So think about the other possibility and put all functionality with side effects into a class. This will be a good idea anyway because “side effect” always means that we have to access some resource. And resource management is one of the main reasons why we have classes. Going back to the serialization example in my opinion it will be a good design to implement a File management class which does the generic file handling without analyzing the file content. The de/serialization of the data, which means converting an object into an xml stream and vise verse, will be implemented within a second class. If we have these two classes we can create our non-member function which writes an object into the file. This non-member function will use both classes and combine their features to create the output file. Of course this will create a file and therefore has a side effect, but the big difference between the direct implementation of the file writer within the non-member function is that the non-member function itself will not create the file and therefore it is not the source for the side effects. It will delegate this functionality to the according experts, in this case the classes which are responsible for the resource management.

The long explanation of the last topic and the small difference between own responsibility for side effects versus delegated responsibility for side effects shows the small difference of these concepts from an implementation point of view. Developers may even argue that they want to create the file directly within the non-member function and avoid the overhead and implement additional classes. But if you have to understand, maintain or extend such source code or if you have to hear user complaints about application errors and you have to write bugfixes based on function with side effects, you will understand why I think that this aspect is so important.

Do not put everything in the class

Within the previous paragraph I addressed an interesting aspect: a non-member function can use one or more classes to fulfill its functionality. I even think that this is the standard use case. An interesting case in this context is a function which uses member functions of one class. Such functions are often implemented. This is understandable because if you implement a class and you want to add some functionality based on the class it feels self-evident to put this function into the class. But as we have seen so far some of these functions are good candidates for non-member functions. Fortunately there is a really easy characteristic to recognize such functions. If a function will use the public interface functions of the class only, it is a perfect candidate for a non-member function. So if you write a class and their member functions you may ask yourself: Does this class use the internal members and resource handling or does it only use the public interface functions? In the first case it should be implemented as class member function and in the second case as non-member function.

Advantages of non-member functions

So far we have seen use cases and guidelines when and how we can implement non-member functions. But why should we implement them at all? We could also put them together into a class. In our example the function to read a single database value can be grouped together with some other database tool functions and we can put them into a database tool or helper class. And if you look into existing code you will sometimes find a lot of “helper”, “tool” or “common” classes.

If we implement the non-member functions as atomic functions without side effects we don’t have any advantage if we put them together in one class. There is no relationship between the functions, except that they belong together from a logical point of view. But we can respect this aspect by putting them into the same namespace and there is no need to put them into a class and therefore connect them in a technical way.

If we leave them disconnected we have less dependency. The client which uses the functions depends on this function only. As a result only a change of the function will result in the need to adapt the client. If we put several functions into one class we increase the probability for changes. Clients will now use the function of the class and therefore they may depend on the class and must be changed or re-compiled every time the class changes, which will of course occur more often then a change of a single function. By using non-member functions we will increase the independence of the function as we create fewer dependencies to clients.

The same aspect will be relevant if we want to add new functionality. In case we have non-member functions we can add new functionality at any time as this will not affect the clients. In contrast, if we add something to a class it may become necessary to update or recompile the client code. By using non-member functions we facilitate the implementation of new functionality.

The atomicity of non-member function will allow us to group them together as we want. We can use one common namespace for all non-member functions or several sub-namespaces with one common parent namespace or any kind of namespace structure. Furthermore we can put these function groups into several files. So we can create functions packages and deploy them independently. By using classes or a system of parent classes and sub classes you may be able to create the same grouping for the functions but it will become more complex and less maintainable then using namespaces. And with classes you don’t have the possibility to split functions of the same group into different files and deploy them separated. You can use the same namespace in different files but you cannot write partial classes in different files. By using non-member functions can group functions in an easy manner and you are able to create and deploy function packages.

Summary

Member functions of classes as well as non-member functions have their own advantages and disadvantages. If you want to benefit of these advantages you have to choose the right kind of function depending on your use case.

If you want to write functions which are strongly coupled as they share the same resources and will be used together by a client you should implement an according class and write class member functions.

If you have atomic functions which do not share or need any resources and can be executed as service-functions without any side effects, you should implement these methods as non-member functions. Several non-member functions which belong to the same topic can be grouped within one namespace.

Advertisements
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s