Namespaces
Learn the essentials of using namespaces to organize your code and handle large projects with ease
In the real world, C++ projects can get very large. They can contain thousands of source files, being collaborated on by multiple teams of people.
In these scenarios, we need to be extra-mindful of how our project is organized. We don't want the work we're doing in our part of the program to conflict with or restrict what is possible in some other component.
For example, if we create a simple variable in one of our files, nobody else can create a variable with the same name, in any of the thousands of other files our program might contain.
Doing so would be a violation of the one definition rule, which we introduce later in this lesson. Both of the following files will compile successfully but, once the output is sent to the linker, it will notice the problem and raise an error:
// SomeFile1.cpp
float Pi{3.14};
`// SomeFile2.cpp
float Pi{3.14};
SomeFile2.cpp.obj : error LNK2005: "float Pi" already defined in SomeFile1.obj
Another issue we'll encounter when working on such large projects is a simple human readability problem.
When we have a file that is creating an object from a Character
class, or callling a function called CalculateArea()
, which part of our project are those symbols coming from? When our project has dozens of components split across thousands of files, it's not always clear what we're using.
To solve both of these problems, we use namespaces.
Creating a Namespace
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;
}
};
}
Test your Knowledge
Creating Namespaces
How can we create a namespace called Utilities
?
Accessing Identifiers Inside Namespaces
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);
}
Test your Knowledge
The Scope Resolution Operator
How can we call the Greet
function within the Utilities
namespace?
namespace Utilities {
void Greet() {
cout << "Hi!";
};
};
Nested Namespaces
In particularly complex projects, even one of our namespaces can get extremely large, containing hundreds or thousands of identifiers. To solve this, we can essentially repeat the process - adding namespaces to our namespace.
That is, 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)
The One Definition Rule (ODR)
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;
}
Adding to Namespaces
In complex 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;
}
Anonymous Namespaces
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
Summary
This lesson provided an introduction to namespaces, covering their creation, usage, and significance in organizing code.
Key Takeaways:
- How to create and use namespaces to organize code and avoid identifier conflicts.
- The role of the scope resolution operator
::
in accessing namespace members. - The concept of nested namespaces and how they can be used for further organization.
- The importance of the One Definition Rule (ODR) in namespaces and strategies to adhere to it using the
extern
andinline
keywords. - The distinction between namespaces and classes in C++, and the introduction to anonymous namespaces for file-specific usage.
Enums
Learn about Enums and how they offer an efficient way to handle predefined values in your code