C++ Circle Code Reorganisation

Simple Reorganisation of Circle Calculator

Algorithmically similar programs, with the same functionality, can be organised differently. Some of the various organisational possibilities are demonstrated here, culminating with the most common practical organisation: multi-file projects. We have also included an option us­ing simple encapsulation.
PREREQUISITES — You should already…
  • have non-trivial C programming experience.
  • be familiar with command line basics in a POSIX shell, Command Prompt, or PowerShell.

Introduction

A C++ compiler reads one file, once, from top to bottom, left to right. Within this frame­work, it does not care how you organise your code. Here we show how the same, simple, program can be re­ar­ran­ged, and still be the same program, i.e., perform exactly the same tasks. We also include a simple ob­ject-oriented organisation, which may be ignored in the short-term, depending on your cur­rent ex­pe­rience.

Simplest Version

In this version, all the code for the execution of the program tasks is in the main() function. It per­forms all I/O and calculations directly.

simpcirc.cppSimplest Circle Example

For such a simple program, that might be all you would ever need. It is the principle that counts, however, and the benefits of reorganisation simply increase with program complexity.

Using Functions

Whenever we foresee or observe the some code used multiple times, we should place it in func­tions. The same program above is presented again below, but it now uses functions. By design, we wanted to read the program from the top, which means we wanted to define the helper func­tions lower down in the file. For that to work, we still had to declare the functions before main().

funccirc.cppCircle with Functions Example

The circcircum() function is more canonical (typical), than the circarea() function. For func­tions this simple, the latter is generally better, but the first represents a more common pattern for more complex functions.

Separate Files

C++ supports separate compilation, which really means that it is not very sophisticated, and nev­er ev­er has any concept of a “program” — it only compiles source files to object files, one at a time, with no memory of the previous files it compiled. The linker makes executables from the object files.

The file containing main() still needs to know about the functions to be called. But instead of keep­ing the declarations in this file, we moved it to our own include file (circfuncs.hpp). The pre­pro­ces­sor will then replace the #include… line with the contents of the file.

main.cppCircle with Functions Main File

The circfuncs.cpp file contains the definitions of the circle calculator functions we may want to re­use in programs. Although not syntactically necessary, we always include the head­er file de­clar­ing the functions in the file defining the functions, so the compiler can check that they match.

circfuncs.cppCircle with Functions Implementation

IMPORTANTPaired Specification & Implementation Files

We cannot stress enough the importance of including xxx.hpp into its cor­re­spon­d­ing xxx.cpp file. It is not always syntactically necessary, but this is the only way we can ensure that dec­la­ra­tions and definitions remain in sync. If there is an unintentional mismatch that is not de­tec­ted, you will have a very-difficult-to-find bug.

circfuncs.cppCircle with Functions Specification

The header file contains an inclusion guard to prevent multiple inclusions in the same com­pi­la­tion unit. The macro name (in this case _CIRCFUNCS_HPP_) is derived from the file name, to help ensure uniqueness. It is a well-es­tab­lish­ed convention, and an easy pattern to implement, even if you might not yet understand the full rationale.

Compiling Multi-File Programs

You can simply add all the source files in your program to the end of your normal command line. This disadvantages compile times, especially when there are many source files. For something this sim­ple, it could look like this:

$> BUILD CIRCLE (1)
g++ -Wall -Werror -std=c++11 -o circle main.cpp circfuncs.cpp

You could also compile each source file (.cpp) separately, but then you must instruct the driver to leave the objects files, and not delete them after the linker is done:

$> BUILD CIRCLE (2)
g++ -Wall -Werror -std=c++11 -c main.cpp
g++ -Wall -Werror -std=c++11 -c circfuncs.cpp
g++ -o circle main.o circfuncs.o

The first two commands create main.o and circfuncs.o respectively, because of the -c switch (means “compile only”, i.e., don’t call the linker). The last command only calls the linker with ob­ject files as arguments — this effective means “linking”, since the driver (g++), will only call the link­er, when given only object files as arguments.

IMPORTANTDo Not Compile Header Files

You never mention header files on the compilation command line. They are not source files in the normal sense, but rather components to be included in other .cpp files.

Encapsulation of Circle

Once some C++ object-oriented features are discovered, further organisation becomes available. In particular, encapsulation, using a class, is now possible. It is generally quite common to re­strict a .cpp file to one class (sometimes called the implementation), with its cor­re­spon­ding head­er file (sometimes called the specification), just like the ordinary multi-file organisation above.

main.cppCircle Encapsulation Main

The main() function above simply exercises some features of the encapsulated Circle class. Al­though not shown, it should be clear that we can have arrays of Circle ob­jects; pass Circle objects as pa­ra­me­ters; and return Circle objects from functions.

It also illustrates a special syntax whereby the whole of a function body can be a try … catch block.

Circle.hppCircle Encapsulation Specification

We show some functions inline inside the class and inline outside the class. Two functions are not inline, and are declared only. They must still be defined in a .cpp source file. We also over­load the in­ser­tion operator for Circle objects. We could have declared it as a friend func­tion in­side the class, but that is only necessary if its implementation required access to pri­vate mem­bers, which this one does not.

Circle.cppCircle Encapsulation Implementation

This is a multi-file program, so all the source files must be compiled and linked together, as shown before. Here is an example GNU make Makefile which can be used with GCC. It should be portable, but does assume an rm command is available (only for the clean target, though).

MakefileCircle Encapsulation Make File

If running make does not work, try mingw32-make instead. With the Incus Data packaging of Min­GW-w64 and utilities, you must run the latter, although we have created an alias for you with doskey in the default startup.cmd file, called gmake. This might help stave off car­pal tun­nel syn­drome from too much typing.

The Makefile example above, does not deal with header files, which is very common. But some­times you want files to be recompiled, if one or more headers change. In that case, the generic na­ture much be modified to be more specific. This means that for each .o file, one must not only spec­i­fy the related .cpp file, but also which headers are considered dependencies.

TIPGenerate Dependencies

You can use: g++ -MM *.cpp to create a list of targets and dependencies.


2017-11-18: Update to new admonitions. [brx]
2017-09-22: Edited. [brx;jjc]
2017-03-20: Created. [brx]