• HOME
  • ABOUT US
  • SERVICES
  • PORTFOLIO
  • BLOG
Connection Quest Connection Quest
  • HOME
  • ABOUT US
  • SERVICES
  • PORTFOLIO
  • BLOG
Connection Quest

Forming class(es)

Home / Clean C++ / Forming class(es)

Forming class(es)

By Marko Jovic inClean C++

In our last post, I talked about objects, data structures and the consequences of your everyday design choices. I haven’t, however, touched on class conventions. Therefore, for this post we have a new quest: make a connection between the design of a class and its functionality.

We’ll start with something that you must have already heard while reading this series: classes should be small. Each class should have only one responsibility, or differently put, one reason to change. This is known as the Single-responsibility principle, one of the core design principles used to make decoupled, flexible and maintainable code.

But that’s not enough. Besides being small, a class should be cohesive. Ideally, each class method uses all class variables – that’s maximum cohesion. Of course, it’s not always feasible but at least you should:

  • strive to create cohesive classes
  • split classes when they lose cohesion
  • make a function a member only if it needs direct access to the representation of a class
  • adhere to the law of Demeter: method f of class C should only call methods of
    • C
    • an object created by f
    • an object passed as an argument to f
    • an object held in an instance variable of C

C++ provides us with a possibility to write header files. We should use them to represent interfaces and to emphasize logical structure. Avoid non-inline function definitions in headers, that’s what source files are for. Include specialized headers first, then generic ones. Separate categories with empty lines.

When declaring class members, follow these recommendations:

  • a class should begin with its public interface
    • public static constants
    • public functions
  • and then follow up with internal details
    • private static variables
    • private instance variables
    • private utility functions

One of the great things about classes is that we can utilize them to effectively manage resources. Resource Acquisition is Initialization (RAII) is a programming technique used to bind the lifecycle of a resource to the lifecycle of an object using it. The idea is to acquire a resource in the constructor and release it in the destructor. As we know, constructors guarantee and simplify object creation, therefore they are the most suitable place to ensure that we have loaded the appropriate resource. Destructor, on the other hand, makes sure that the object will clean up after itself, not leaving any leaks behind.

class IntCollection {
public:
	IntCollection(int length) {
		elements = new int[length]{};
	}
	…
	~IntCollection() {
		delete[] elements;
	}
private:
	int *elements { nullptr };
	…
}

In the class design phase, think about essential operations. In most cases, they are designed as a matched set so you should define all of them or none.

  • constructor
  • destructor
  • copy assignment
  • move assignment

Naturally, if a default operation is appropriate, let the compiler generate it.

Consider the use cases. A class with a pointer member probably needs a user-defined or deleted destructor. Furthermore, a class with a destructor probably needs user-defined or deleted copy and move operations.

class IntPointer {
public:
	// Move constructor
	IntPointer(IntPointer &&other) {
		ptr = other.ptr;
		other.ptr = nullptr;
	}
	…
	// Move assignment
	IntPointer& operator=(IntPointer &&other) {
		...
	}
	...
	~IntPointer() {
		delete ptr;
	}
private:
	int *ptr { nullptr };
	…
}

As you surely know, we achieve polymorphism with abstract classes and virtual functions. If creating them, take note of the following:

  • interfaces are typically created without a constructor
  • to ensure that the proper destructor is called, a class with a virtual function should have a virtual destructor
  • use override keyword to make overriding explicit
  • when reimplementing a virtual method, do not put the virtual keyword in the header file
  • only use dynamic_cast where class hierarchy navigation is unavoidable

To avoid potentially unwanted conversions, define single-argument constructors as explicit by default.

class MyInt {
public:
	int number;
	…
	explicit MyInt(int number) {
		...
	}
}

MyInt myNumber = ‘x’; // compile-error, explicit constructor does not allow conversion

And last, but not least, to specify that a member function does not modify the state of its object, mark it as const.

class MyInt {
public:
	int getValue() const {
		return number;
	}
private:
	int number;
	…
}

Try to follow these recommendations and specify the functionality of your classes by properly designing and organizing them.

Stay tuned and happy coding.

AdviceC++ClassesClean CodeConventionsObjectsOOPSoftwareSoftware Development
15 Posts
Marko Jovic
  • Objects vs. Data Structures
    Previous PostObjects vs. Data Structures
  • Next PostJava Native Interface with Qt
    Objects vs. Data Structures

Leave a Reply (Cancel reply)

Your email address will not be published. Required fields are marked *

*
*

© 2025 Connection Quest j.d.o.o. All rights reserved.

Copy