10 Best practices to design and implement a C++ class

lahlali issam
7 min readMay 4, 2021

--

1- Try using new C++ standards as possible

By 2011, C++ had been in use for more than 30 years. It was not easy to convince developers that the new C++ standards actually simplified many frustrating facets of C++ usage and that there was a new modern way to improve the C++ Code.

Let’s take memory management as an example, which is maybe the most criticized mechanism in C++. For many years the object allocation was done by the new keyword, and the developer must never forget to invoke delete somewhere in the code. The “Modern C++” resolved this issue and promotes the use of a shared pointer.

2- Modularize your code using namespaces

Modern C++ libraries use extensively the namespaces to modularize their codebase, and they use the “Namespace-by-feature” approach. Namespace-by-feature uses namespaces to reflect the feature set. It places all items related to a single feature (and only that feature) into a single namespace. This results in namespaces with high cohesion and high modularity, and with minimal coupling between namespaces. Items that work closely together are placed next to each other.

Boost is the best example of grouping by feature, it contains thousands of namespaces, each one is used to group a specific feature.

3- Abstraction

Data abstraction is one of the most essential and important features of object-oriented programming in C++. Abstraction means displaying only essential information and hiding the details. Data abstraction refers to providing only essential information about the data to the outside world, hiding the background details or implementation.

Even if this best practice is recommended by many books, web resources, conference speakers, experts. However, in many projects, this rule is ignored and we can have many class details not hidden.

4- Small classes are better

Types with many lines of code should be split into a group of smaller types.

To refactor a big Class you’ll need patience, and you might even need to recreate everything from scratch. Here is some refactoring advice:

• The logic in the BigClass must be split into smaller classes. These smaller classes can eventually become private classes nested in the original God Class, whose instances objects become composed of instances of smaller nested classes.

• Smaller class partitioning should be driven by the multiple responsibilities handled by the God Class. To identify these responsibilities it often helps to look for subsets of methods strongly coupled with subsets of fields.

• If the Big Class contains way more logic than states, a good option can be to define one or several static classes that contains no static field but only pure static methods. A pure static method is a function that computes a result only from inputs parameters, it doesn’t read nor assign any static or instance field. The main advantage of pure static methods is that they are easily testable.

• Try to maintain the interface of the Big Class at first and delegate calls to the new extracted classes. In the end, the Big Class should be a pure facade without its own logic. Then you can keep it for convenience or throw it away and start to use the new classes only.

• Unit Tests can help: write tests for each method before extracting it to ensure you don’t break functionality.

5- Few methods per class as you can

Types with more than 20 methods might be hard to understand and maintain.

Having many methods for a type might be a symptom of too many responsibilities implemented.

Maybe you are facing a class that controls way too many other classes in the system and has grown beyond all logic to become The Class That Does Everything.

6- Enforce Low coupling

Low coupling is desirable because a change in one area of an application will require fewer changes throughout the entire application. In the long run, this could alleviate a lot of time, effort, and cost associated with modifying and adding new features to an application.

Low coupling could be achieved by using abstract classes or using generic types and methods.

7-Enforce High cohesion

The single responsibility principle states that a class should not have more than one reason to change. Such a class is said to be cohesive. A high LCOM value generally pinpoints a poorly cohesive class. There are several LCOM metrics. The LCOM takes its values in the range [0–1]. The LCOM HS (HS stands for Henderson-Sellers) takes its values in the range [0–2]. A LCOM HS value highest than 1 should be considered alarming. Here are to compute LCOM metrics:

LCOM = 1 — (sum(MF)/M*F)
LCOM HS = (M — sum(MF)/F)(M-1)

Where:

  • M is the number of methods in class (both static and instance methods are counted, it includes also constructors, properties getters/setters, events add/remove methods).
  • F is the number of instance fields in the class.
  • MF is the number of methods of the class accessing a particular instance field.
  • Sum(MF) is the sum of MF over all instance fields of the class.

The underlying idea behind these formulas can be stated as follow: a class is utterly cohesive if all its methods use all its methods use all its instance fields, which means that sum(MF)=M*F and then LCOM = 0 and LCOMHS = 0.

LCOMHS value higher than 1 should be considered alarming.

8- Comment only what the code cannot say

Comments that parrot the code offer nothing extra to the reader. A prevalence of noisy comments and incorrect comments in a codebase encourages programmers to ignore all comments, either by skipping past them or by taking active measures to hide them.

9- Don’t repeat yourself as possible

It’s known that the presence of duplicate code has negative impacts on software development and maintenance. Indeed a major drawback is when an instance of duplicate code is changed for fixing bugs or adding new features, its correspondents have to be changed simultaneously.

The most popular reason for duplicate code is the Copy/Paste operations, and in this case, the source code is exactly similar in two or more places, this practice is discouraged in many articles, books, and websites. However, sometimes it’s not easy to practice the recommendations, and the developer chose the easy solution: the Copy/Paste method.

Using the appropriate tool makes easy the detection of the duplicate code from the copy/paste operations, however, there are some cases where cloned code is not trivial to detect.

10- Immutability is your friend for multithreading programming.

Basically, an object is immutable if its state doesn’t change once the object has been created. Consequently, a class is immutable if its instances are immutable.

There is one important argument in favor of using immutable objects: It dramatically simplifies concurrent programming. Think about it, why does writing proper multithreaded programming is a hard task? Because it is hard to synchronize threads access to resources (objects or others OS resources). Why it is hard to synchronize these accesses? Because it is hard to guarantee that there won’t be race conditions between the multiple write accesses and read accesses done by multiple threads on multiple objects. What if there are no more write accesses? In other words, what if the state of the objects accessed by threads, doesn’t change? There is no more need for synchronization!

Another benefit about immutable classes is that they can never violate LSP (Liskov Subtitution Principle) , here’s a definition of LSP quoted from its wiki page:

Liskov’s notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).

The Class of the object referenced is immutable if it has no public fields, no methods that can change its internal data, and no way for methods of the derived class to change its internal data. And because the value cannot change, it’s possible to reference the same object in all cases. There’s no need to have a copy constructor or assignment operator. For this reason, it’s recommended to make the copy constructor and assignment operator private, inherit from boost::noncopyable, or use the new C++11 feature “Explicitly defaulted and deleted special member functions“.

How to enforce the check of these best practices?

CppDepend provides a code query language named CQLinq to query the code base like a database. Developers, designers, and architects could define their custom queries to find easily the bug-prone situations.

With CQlinq we can combine the data from the code metrics, dependencies, API usage, and other model data to define very advanced queries that match some bug-prone situations.

For example, after the analysis of the clang source code, we can detect the big classes:

Detect classes with many methods:

Or detect classes with poor cohesion:

--

--