When working with bigger projects, or importing additional third-party code using the #include
directive, we can run into further organization problems.
When we use a class like Character
or call a function like CalculateArea()
it can be unclear where that identifier is coming from.
Additionally, the larger our project becomes, the more likely it is we have conflicts around these identifiers. We’ll have scenarios where multiple variables, functions, or classes use the same name.
The way this problem is solved is by the introduction of namespaces
.
We can wrap sections of our code inside a namespace
as shown below. Here, we create a namespace called Geometry
:
namespace Geometry {
// your code here
}
We can populate namespaces with classes, functions, variables, and any of the constructs we've seen before:
namespace Geometry {
float Add(float x, float y) {
return x + y;
}
float Pi { 3.14 };
class Square {
float SideLength { 5.0 };
float Area() {
return SideLength * SideLength;
}
};
}
How can we create a namespace called Utilities
?
As usual with {
and }
, namespaces define a new scope. Expressions within the namespace can access other namespace members as usual:
namespace Geometry {
float Pi { 3.14 };
float Circumference(float Diameter) {
// We can access Pi here as we're in
// the same scope / namespace
return Diameter * Pi;
}
}
To access namespace members from outside the namespace, we need the Scope Resolution Operator, which has two colons ::
For example, the following statement accesses the Add
function and the Pi
variable, which are both inside the Geometry
namespace:
namespace Geometry {
float Add(float x, float y) {
return x + y;
}
float Pi { 3.14 };
}
int main() {
Geometry::Add(10, Geometry::Pi);
}
How can we call the Greet
function within the Utilities
namespace?
namespace Utilities {
void Greet() {
cout << "Hi!";
};
};
Namespaces can be nested inside other namespaces. Below, we have a namespace called Constants
within a namespace called Geometry
:
namespace Geometry {
float Add(float x, float y) {
return x + y;
}
namespace Constants {
float Pi { 3.14f };
}
}
To access these nested identifiers, we can use the ::
operator multiple times:
Geometry::Add(10, Geometry::Constants::Pi)
A common question people have when deciding how to organize code in C++ projects is when to use a namespace and when to use a class. The main conceptual difference is that classes can create objects, whilst namespaces cannot.
It doesn't make sense to create an object with a type of "Geometry". So, for this type of organization, we can create a namespace instead.
Those coming from other programming languages may be familiar with the concept of a static class, which is how these problems are sometimes solved in those programming languages.
C++ also allows class members to be static, which we'll cover soon.
However, in most scenarios, solving this problem directly using namespaces tends to be the preferred approach in C++.
Once we create a namespace and #include
it in other files, we’ll quickly begin to see linker errors relating to symbols already being defined. The following simple 3-file program recreates this:
// Geometry.h
#pragma once
namespace Geometry {
float Pi{3.14};
float Circumference(float Diameter) {
return Diameter * Pi;
}
}
// main.cpp
#include "Geometry.h"
int main() {}
// SomeFile.cpp
#include "Geometry.h"
main.cpp.obj : error LNK2005: "float Geometry::Circumference(float)" already defined in SomeFile.obj
main.cpp.obj : error LNK2005: "float Geometry::Pi" already defined in SomeFile.obj
To solve this, we can split our namespace into a header file and source file, in much the same way we have shown with classes.
Our declarations remain in Geometry.h
whilst we move definitions to Geometry.cpp
, as in the following example. Note we explain the meaning of extern
in the next section:
// Geometry.h
#pragma once
namespace Geometry {
extern float Pi;
float Circumference(float Diameter);
}
// Geometry.cpp
#include "Geometry.h"
float Geometry::Pi{3.14};
float Geometry::Circumference(float Diameter) {
return Diameter * Pi;
}
extern
keywordWhen we’re forward declaring a function that will be defined externally, the compiler can implicitly understand that’s what we’re doing. When a function has no body, the compiler understands we’re not defining the function - we’re just declaring it.
With variables, we need to do a little extra work.
A statement like int MyInt;
defines a variable. Specifically, we’re defining it to be whatever is the result of calling the default constructor of that type. In this case, we’re defining MyInt
as 0
.
When we intend to forward-declare a variable that is defined externally, in some other file, we need to clarify that intent by adding the extern
keyword.
inline
keywordAs of C++17, we also have the option of marking the members inline
rather than moving their definition to a dedicated file:
// Geometry.h
#pragma once
namespace Geometry {
inline float Pi{3.14};
inline float Circumference(float Diameter) {
return Diameter * Pi;
}
}
If the compiler receives multiple definitions of an inline
member, it simply chooses one and discards the others.
This is intended to specifically solve multiple definitions caused by the #include
directive. In such scenarios, every definition will be the same, so it doesn’t matter what the compiler chooses.
We cover extern
, inline
, and related topics in more detail in the next course.
A common point of confusion at this point is why our class definitions haven’t infringed on the one definition rule when we include their headers in multiple files.
For example, it would seem like a class like this would generate multiple definitions of the Health
variable when included in multiple files:
// Character.h
#pragma once
class Character {
public:
int Health{100}
};
But when we define a class member in this way, the variable is not technically part of the class. It is simply a recipe for creating instances of a variable that will exist on objects created using the class.
On a namespace, however, we are generating a single variable, accessible using a specific identifier like Geometry::Pi
. There will only ever be one instance of that variable in our program, so we can only have one definition for what its value should be.
It is possible to create class variables that behave like namespace variables. These are called static class members, which we’ll introduce in the next course.
In large projects, namespaces can get quite large. They can include multiple classes, for example. As such, it is common for the contents of a namespace to span multiple files.
Below, we create a namespace containing two classes, where each class is declared within a dedicated file:
// Square.h
#pragma once
namespace Geometry {
class Square{};
}
// Circle.h
#pragma once
namespace Geometry {
class Circle{};
}
// main.cpp
#include "Circle.h"
#include "Square.h"
int main() {
Geometry::Circle MyCircle;
Geometry::Square MySquare;
}
Where we’re providing a full declaration of our namespace within a header file, we can alternatively provide class definitions by including that header file and then using the ::
operator like this:
// Geometry.h
#pragma once
namespace Geometry {
class Circle;
}
// Circle.h
#pragma once
#include "Geometry.h"
class Geometry::Circle {
};
When our class is within a namespace, our source files for that class can provide function definitions using either the namespace
syntax, or the ::
operator:
// Square.h
#pragma once
namespace Geometry {
class Square{
public:
float Area();
float Perimeter();
private:
float SideLength;
};
}
// Square.cpp
#include "Square.h"
// Option 1:
namespace Geometry {
float Square::Area() {
return SideLength * SideLength;
}
}
// Option 2:
float Geometry::Square::Perimeter() {
return SideLength * 4;
}
Previously, we introduced the concept of global variables. These are variables that are added to our files, outside of any scope. In the following example, Pi
is a global variable:
// main.cpp
float Pi;
int main() {}
We saw how the linker can link a definition in one file to its use in another.
However, this presents a problem when there are multiple definitions of the same identifier.
For example, if we create a global Pi
variable in another file, our program will no longer link correctly:
// Geometry.cpp
float Pi;
main.cpp.obj : error LNK2005: "float Pi"
already defined in Geometry.obj
This is again a violation of the one-definition rule (ODR).
If we intend to forward-declare a variable that will be defined in some other file, we can add the extern
keyword as described above:
// Geometry.cpp
float Pi { 3.1415 };
// main.cpp
#include <iostream>
using namespace std;
extern float Pi;
int main() {
cout << "Pi as defined in some other file: "
<< Pi;
}
Pi as defined in some other file: 3.1415
If we intend to create a variable that we can use anywhere in the current file, without affecting any other file, we can solve the problem using an anonymous namespace.
Predictably, an anonymous namespace is a namespace without a name:
namespace {
float Pi { 3.14 };
}
Identifiers defined within an anonymous namespace are only available to the same source file where the namespace exists.
Therefore, we can think of an anonymous namespace as the "private" section of a file.
// Geometry.cpp
namespace {
float Pi{3.1415};
}
float GetPi() { return Pi; }
// main.cpp
#include <iostream>
using namespace std;
namespace {
float Pi{3.14};
}
// Forward declaring a function defined
// in Geometry.cpp
float GetPi();
int main() {
cout << "Pi in main.cpp: " << Pi
<< "\nPi in Geometry.cpp: " << GetPi();
}
Pi in main.cpp: 3.14
Pi in Geometry.cpp: 3.1415
This lesson provided an introduction to namespaces, covering their creation, usage, and significance in organizing code.
Key Takeaways:
::
in accessing namespace members.extern
and inline
keywords.In our next lesson, we'll delve into the world of enums. Enums, or enumerations, are yet another way to create user-defined types, but they are much simpler than classes and structs.
They are particularly useful in scenarios when we need to create variables that have a value from a limited range of possibilities, which we can define.
Key Topics:
Learn the essentials of using namespaces to organize your code and handle large projects with ease
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way