Namespaces

Learn the essentials of using namespaces to organize your code and handle large projects with ease
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
3D art showing a fantasy character
Ryan McCombe
Ryan McCombe
Edited

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.

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

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)

Namespaces vs Classes

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++.

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;
}

The extern keyword

When 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.

The inline keyword

As 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.

Why don’t class declarations infringe on the one-definition rule?

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.

Adding to Namespaces

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;
}

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.

Preview of the Next Lesson

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:

  • Understanding the basic concept of enums and their role in programming.
  • Learning how to define and use enums to represent a collection of related values.
  • Exploring the benefits of using enums for code readability and maintenance.

Was this lesson useful?

Edit History

  • Refreshed Content

  • First Published

Ryan McCombe
Ryan McCombe
Edited
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Namespaces and Enums
    40.
    Namespaces

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


3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

  • 56 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

Enums

Learn about Enums and how they offer an efficient way to handle predefined values in your code
3D art showing a fantasy character
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved