31. Organizing code – Modern C++ for Absolute Beginners: A Friendly Introduction to C++ Programming Language and C++11 to C++20 Standards

© Slobodan Dmitrović 2020
S. DmitrovićModern C++ for Absolute Beginnershttps://doi.org/10.1007/978-1-4842-6047-0_31

31. Organizing code

Slobodan Dmitrović1 
(1)
Belgrade, Serbia
 

We can split our C++ code into multiple files. By convention, there are two kinds of files into which we can store our C++ source: header files (headers) and source files.

31.1 Header and Source Files

Header files are source code files where we usually put various declarations. Header files usually have the .h (or .hpp) extension. Source files are files where we can store our definitions and the main program. They usually have the .cpp (or .cc) extension.

Then we include the header files into our source files using the #include preprocessor directive. To include a standard library header, we use the #include statement followed by a header name without an extension, enclosed in angle brackets <headername>. Example:
#include <iostream>
#include <string>
// etc
To include user-defined header files, we use the #include statement, followed by a full header name with extension enclosed in double-quotes. Example:
#include "myheader.h"
#include "otherheader.h"
// etc
The realistic scenario is that sometimes we need to include both standard-library headers and user-defined headers:
#include <iostream>
#include "myheader.h"
// etc

The compiler stitches the code from the header file and the source file together and produces what is called a translation unit . The compiler then uses this file to create an object file. A linker then links object files together to create a program.

We should put the declarations and constants into header files and put definitions and executable code in source files.

31.2 Header Guards

Multiple source files might include the same header file. To ensure that our header is included only once in the compilation process, we use the mechanism called header guards . It ensures that our header content is included only once in the compilation process. We surround the code in our header file with the following macros:
#ifndef MY_HEADER_H
#define MY_HEADER_H
// header file source code
// goes here
#endif

This approach ensures the code inside a header file is included only once during the compilation phase.

31.3 Namespaces

So far, we have seen how to group parts of our C++ code into separate files called headers and source files . There is another way we can logically group parts of our C++, and that is through namespaces. A namespace is a scope with a name. To declare a namespace, we write:
namespace MyNameSpace
{
}
To declare objects in a namespace, we use:
namespace MyNameSpace
{
    int x;
    double d;
}
To refer to these objects outside the namespace, we use their fully qualified names. This means we use the namespace_name::our_object notation. An example where we define the objects outside the namespace they were declared in:
namespace MyNameSpace
{
    int x;
    double d;
}
int main()
{
    MyNameSpace::x = 123;
    MyNameSpace::d = 456.789;
}
To introduce an entire namespace into the current scope, we can use the using -directive:
namespace MyNameSpace
{
    int x;
    double d;
}
using namespace MyNameSpace;
int main()
{
    x = 123;
    d = 456.789;
}
If we have several separate namespaces with the same name in our code, this means we are extending that namespace. Example:
namespace MyNameSpace
{
    int x;
    double d;
}
namespace MyNameSpace
{
    char c;
    bool b;
}
int main()
{
    MyNameSpace::x = 123;
    MyNameSpace::d = 456.789;
    MyNameSpace::c = 'a';
    MyNameSpace::b = true;
}

We now have x, d, c, and b inside our MyNameSpace namespace. We are extending the MyNameSpace, not redefining it.

A namespace can be spread across multiple files, both headers and source files. We will often see production code wrapped into namespaces. It is an excellent mechanism to group the code into namespaces logically.

Two namespaces with different names can hold an object with the same name. Since every namespace is a different scope, they now declare two different unrelated objects with the same name. It prevents name clashes:
#include <iostream>
namespace MyNameSpace
{
    int x;
}
namespace MySecondNameSpace
{
    int x;
}
int main()
{
    MyNameSpace::x = 123;
    MySecondNameSpace::x = 456;
    std::cout << "1st x: " << MyNameSpace::x << ", 2nd x: " << MySecondNameSpace::x;
}