Namespaces

Learn the essentials of using namespaces to organize your code and handle large projects with ease

Ryan McCombe
Updated

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 and inline keywords.
  • The distinction between namespaces and classes in C++, and the introduction to anonymous namespaces for file-specific usage.
Next Lesson
Lesson 42 of 60

Enums

Learn about Enums and how they offer an efficient way to handle predefined values in your code

Have a question about this lesson?
Answers are generated by AI models and may not have been reviewed for accuracy