Explore the design of a modern C++ library : MemCache++ case study.

MemCache++ is a light-weight, type-safe, simple to use and full-featured Memcache client. It was developed by Dean Michael Berris who is a C++ fanatic and currently works at Google Australia. He also is part of the Google delegation to the ISO C++ Committee.

Studying the well-designed libraries is recommended to elevate your  C++ design and implementation skills, and the goal of this article is to discover some memcache++ design choices that make it easy to understand and use.

Namespace Modularity

Namespaces are a good choice to modularize the application, unfortunately, this artifact is not enough used in the C++ projects, just a look at a random open source C++ projects show us this fact. Moreover, when We search for the C++ namespace definition the common one is like that:

A namespace defines a new scope. They provide a way to avoid name collisions.

Many times the collision is shown as the first motivation, and not the modularity unlike for C# and Java where namespaces are more quoted to modularize the application.
However, Some modern C++ libraries like boost use the namespaces to structure well the library and encourage the developers to use them.

What about the namespace modularity of memcache++?

Here’s the dependency graph between memcache++ namespaces:


The namespaces are used for two main reasons:

    • Modularize the library.
    • Hide details like “memcache::detail” namespace, this approach could be very interesting if we want to inform the library user that he doesn’t need to use directly the types inside this namespace. For C# the “internal” keyword did the job, but in C++ there’s no way to hide public types to the library user.

memcache++ exploit gracefully the namespace artifact.However, a dependency cycle exists between memcache and memcache::detail. We can remove this dependency cycle by searching types used by memcache::detail from memcache.

For that we can execute the following CQlinq request:

from t in Types where t.IsUsedBy("memcache.detail") 
&& t.ParentNamespace.Name=="memcache"
select new { t,t.TypesUsingMe }

Here’s the result after executing the request:

 

To remove the dependency cycle we can move pool_directive and server_pool_test to the memcache namespace.

Which paradigm is most used in modern C++ code: Generic or OOP?

In the C++ world two schools are very popular: Object Oriented Programming and Generic programming, each approach has their advocates, this article explain the tension between them.

 

What paradigm is the most used by memcache++?

To answer this question Let’s search first for generic types:

from t in Types where t.IsGeneric && !t.IsThirdParty select t 

What about the not generic ones:

from t in Types where !t.IsGeneric && !t.IsGlobal && !t.IsNested 
&& !t.IsEnumeration  && t.ParentProject.Name=="memcache"
select t

Almost all the not generic types concern the exception classes, and to have a better idea of their use proportion, the treemap view is very useful.

The blue rectangles represent the result of the CQLinq query, and as we can see only a minimal part of the library are related to the not generic types.

Finally, we can search for generic methods:

from m in Methods where m.IsGeneric && !m.IsThirdParty select m

As we can observe memcache++ use mostly generics, but it’s not sufficient to confirm that it follows the C++ generic approach.To check that a good indicator is the use of inheritance and dynamic polymorphism, indeed OOP use them mostly. However for the generic approach using inheritance is very limited and the dynamic polymorphism is avoided.

Let’s search for types having base classes.

from t in Types where t.BaseClasses.Count()>0 && !t.IsThirdParty 
&& t.ParentProject.Name=="memcache"
select t


It’s normal that the exceptions classes use the inheritance but what about the other classes?  did they use the inheritance for dynamic polymorphism purpose?  to answer this question let’s search for all the virtual methods.

from  m in Methods where m.IsVirtual select m


Only the exception class has a virtual method.

 

If the dynamic polymorphism is not used, what’s the solution adopted if we need another behavior for specific classes?

The common solution for the modern C++ approach is to use Policies. Here’s a short definition from wikipedia

"The central idiom in policy-based design is a class template(called the host class),taking several type parameters as input, which are instantiated with types selected by the user (called policy classes), each implementing a particular implicit interface (called a policy)."

memcache++ has many policies inside memcache.policies namespace.


Let’s discover an example from memcache++ to understand better the policy based design.

memcache++ use the type basic_handle to implement all commands like add, set, get and delete from the cache.This class is defined like that:

    template <
        class threading_policy = policies::default_threading, 
        class data_interchange_policy = policies::binary_interchange,
        class hash_policy = policies::default_hash
    >
    struct basic_handle 

The memcache++ is thread safe and for the multithreaded context have to manage the synchronization, by default the threading_policy is “default_threading” where no special treatment is required. However, for multithreading the policy used is “boost_threading”.

Let’s take a look to connect method implementation.

    void connect(boost::uint64_t timeout = MEMCACHE_TIMEOUT) { 
       typename threading_policy::lock scoped_lock(*this);
       for_each(servers.begin(), servers.end(), connect_impl(service_, timeout));
    };

If threading_policy is “default_threading”, the first line has no effect because the lock constructor did nothing. However, if it’s the boost_threading one, lock use boost to synchronize between threads.

Using policies give us more flexibility to implement different behaviors, and it’s not very difficult to understand and use.

Generic Functors

memcache++ implement many commands to interact with the cache like add,get,set and delete.The command pattern is a good candidate for such case.memcache++ implement this pattern by using generic functors, here’s a CQLinq query to have all functors:

from t in Types where t.Methods.Where(a=>a.IsOperator 
&& a.Name.Contains("()")).Count()>0
select t

Functor encapsulates a function call, with its state, and it can be used to defer call to a later time, and act as callback.Generic functors give more flexibility to normal functors.

Public Interface exposed

How the library exposes its capabilities is very important, it impacts its flexibility and its ease of use.
To discover that let’s search for the communication between the test project and the memcache++ library.

from m in Methods where m.IsUsedBy ("test")
select m 

The test project uses mainly generic methods to invoke memcache++ functionalities. What the benefits using template methods, why not use classes or functions?

For OOP approach the library interface is composed of classes and functions, and for well-designed ones Abstract classes are used as contracts to enforce low coupling, this solution is very interesting but have some drawbacks:

  • The interface becomes more complicated and can change many times: to explain that let’s take as an example the add method exposed by memcache++, if we don’t use the generic approach many methods must be added, each one for a specific type int,double,string,…

The generic add method is declared like that add<T> where T is the type, and in this case, we need only one method, and even we want to add another type no change required in the interface.

  • The interface is less flexible: if for example, we expose a method like this

calculte(IAlgo* algo).

The user must give a class inheriting from IAlgo , however if we use generic and define it like that calculate<T >, the user has only to provide a class with methods needed and not necessarily inherit from IAlgo, and if IAlgo change to IAlgo2 because some methods are added, the user of this library will not be impacted.

Ideally, the interface exposed by a library must not have any breaking changes, and the user has not to be impacted when changes are introduced in the library. the generic approach is the most suitable for such constraints because it’s very tolerant when changes are needed.

External API used

Here are the external types used by memcache++

memcache++ use mostly boost and STL to achieve its needs, here are some boost features used:

  •  multithreading.
  •  algorithm.
  •  spirit.
  • asio.
  • unit testing.

And from STL the containers are mostly used.

So finally what The advantages of using the generic approach?

  •  The first indicator of the efficiency of memcache++ design choices is the Line number of code(LOC) where is only around 600 lines of code, this result is due to two main reasons:
  •  using generic approach remove boilerplate code.
  •  using the richness of boost and stl.
  •  The second force is its flexibility, and any changes will impact only a minimal portion of code.