Namespaces, Includes, and the Standard Library

A quick introduction to namespaces in C++, alongside the standard library and how we can access it
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

This lesson is a quick introductory tour of functions within C++. It is not intended for those who are entirely new to programming. Rather, the people who may find it useful include:

  • those who have completed our introductory course, but want a quick review
  • those who are already familiar with programming in another language, but are new to C++
  • those who have used C++ in the past, but would benefit from a refresher

It summarises several lessons from our introductory course. Those looking for more thorough explanations or additional context should consider completing Chapters 7 and 8 of that course.

Previous Course

Intro to Programming with C++

Starting from the fundamentals, become a C++ software engineer, step by step.

Screenshot from Cyberpunk 2077
Screenshot from The Witcher 3: Wild Hunt

The #include Directive

The most typical way we import code defined in another file is through an #include directive:

// Same Directory
#include "math.cpp"

// Child Directory
#include "some-subfolder/math.cpp"

// Parent Directory
#include "../math.cpp"

A directive is something we include in our code that will be detected and acted upon by the C++ preprocessor.

The preprocessor is a tool that sits between our code and the compiler. Based on any directives we have, the preprocessor modifies our source code before it is received by the compiler. We cover the preprocessor in more detail later.

The include directive performs a crude copy-and-paste operation on our code before it is compiled. Because of this, we can just use included code as if it were in the same file we’re working on:

// math.cpp
int add(int x, int y) {
  return x + y;
}
// main.cpp
#include "math.cpp"

int main() {
  Add(x+y);
}

In a project with a more complicated web of include directives, this recursive copy-and-paste process is liable to attempt to include the same file twice:

// geometry.cpp
float Pi{3.14f};
// math.cpp
#include "geometry.cpp"
// main.cpp
#include "geometry.cpp"
#include "math.cpp"

After resolving all the #include directives, main.cpp is including geometry.cpp and then indirectly including it again through math.cpp

This will result in a compilation error because we’re now defining a variable called Pi twice. To solve this, for any file that is intended to be included in other files, we should add the #pragma once directive to ensure it never gets included more than once:

// geometry.cpp
#pragma once

float Pi{3.14f};
// math.cpp
#pragma once

#include "geometry.cpp"

Namespaces

Namespaces are the primary way to organize code in large applications. We give our namespace a meaningful name, and then open a set of braces, thereby creating a new scope.

namespace Math {
  // new scope
}

Within these braces, we can create variables, functions, and other constructs in the usual way:

namespace Math {
  int Add(int x, int y) {
    return x + y;
  }

  // Namespaces can be nested
  namespace Geometry {
    float Pi{3.14f};
  }
}

Scope Resolution Operator ::

Access to namespaces, and different scopes in general, can be done using the scope resolution operator ::

For example, to access a variable or function within the scope we called Math, we would prepend the identifier with Math::

Math::Add(1, 2);

Our Math namespace has a nested Geometry namespace, which we can access using multiple scope resolution operators:

Math::Geometry::Pi / 2;

Shadowed Variables

Multiple variables with the same name can be accessible within the same scope. In the following example, within the Math namespace, two variables identified by x are available:

int x{1};

namespace Math{
  int x{2};
}

We have the x defined within the global namespace, which has a value of 1, and the x defined within the local namespace, which has a value of 2

This is sometimes referred to as shadowed variables or shadowed identifiers. It is something we should try to avoid, as it can be confusing and cause bugs.

When resolving an identifier, the compiler will use the one in the scope that is most local to the expression that is using the identifier. As such, in the following example, Math::Value will be 2:

int x{1};

namespace Math{
  int x{2};
  int Value{x}; // 2
}

Access to the global scope is available using the unary scope resolution operator. That is, :: without a left operand. In the following example, Math::Value will now be 1:

int x{1};

namespace Math{
  int x{2};
  int Value{::x}; // 1
}

The Standard Library

C++ comes with a large standard library, which contains a large number of generally useful code. Standard library functionality is typically included using < and > as part of an #include directive.

One of the most common standard library features we will want is the ability to create strings:

// The standard library's string implementation
#include <string>

Strings, and most standard library types and functionality are available within the std namespace:

#include <string>

int main() {
  std::string Greeting{"Hello"};
}

std::cout

By including <iostream> we get access to some useful input and output functionality.

// Ways to interact with input and output
#include <iostream>

The std::cout object allows us to stream content to our terminal using the << operator, thereby letting us see program output:

#include <string>
#include <iostream>

int main() {
  std::string Greeting{"Hello"};
  std::cout << Greeting;
}

This program yields the following output:

Hello

The << operator returns the reference to std::cout, so we can keep streaming content to it as a larger expression:

#include <string>
#include <iostream>

int main() {
  std::string Greeting{"Hello"};

  // The << operator can be chained
  std::cout << Greeting << " World";
}
Hello World

We can insert line breaks into our output using the \n sequence, or by streaming a std::endl token:

#include <iostream>

int main() {
  std::cout << "After this, there is a break\n";
  std::cout << "after this too!" << std::endl;
  std::cout << "This will\nspan two lines";
}
After this, there is a break
after this too!
This will
span two lines

using Statements

When we’re repeatedly going to be using a variable or function from a namespace, it can be helpful to add a using statement. This can be within the global scope, where it will apply to an entire file, or just within the scope of a specific block:

void MyFunction() {
  using std::cout, std::endl;

  // In the rest of this function, we can now
  // use cout and endl without needing to
  // specify the std:: prefix
  cout << "This works!" << endl;
}

If we want to refer to a whole namespace without qualification, we can have a using namespace statement:

void MyFunction() {
  using namespace std;

  // Now, anything in the std namespace can be
  // used, without needing prefixed with std::
  cout << "This works!" << endl;
}

The using namespace statement should generally be restricted, as it bypasses the purpose of having a namespace in the first place.

When files get more complex, it can be difficult to determine where functions and variables are coming from if we omit namespace qualifiers.

If we do want to have using namespace statements, we should prefer using them just within the specific function we need them, rather than across an entire file.

This is particularly true when we’re writing code that we are going to #include in other files, because then our global using statement will affect those files, too.

Literals in Namespaces

Another scenario where we may want to use a using namespace statement is to gain access to more literals.

Under the hood, literals are simply functions, and are typically defined within namespaces. We’ll see examples of this later in the course, when we learn to create literals for our own custom types.

For now, we can see an example of this using a literal for creating std::string, which is available within the std namespace.

We gain access to it through an appropriate using namespace statement, such as:

  • using namespace std
  • using namespace std::literals
  • or using namespace std::string_literals

In any scope where such a statement is in effect, we can use double-quotes with an s suffix, such as "Hello"s to create a std::string:

#include <string>

int main(){
  using namespace std::string_literals;

  // Creates a std::string
  auto MyString{"Hello World"s};
}

String and Character Literals: '', "", and ""s

When it comes to strings and characters, there are three main literals we’ll use in this course:

  • Single quotes, such as 'H' create a char - a built-in type that represents a single character
  • Double quotes, such as "Hello" is an array of char objects. This is a simple way of representing a string.
  • Double quotes suffixed with an s, such as "Hello"s create a std::string. This is a modern string type with many more capabilities than a simple character array.

The std::string type can be created from character arrays, so the simple "" literal is often used even when the result is a std::string:

// Creating a std::string from a char array
std::string MyString{"Hello"};

We’ll explore characters and strings in much more detail throughout this course.

Summary

In this lesson, we explored the basics of organizing code using namespaces. We also discovered how to use #include directives to use code from other files and libraries, and how to use the standard library to access powerful pre-built functionality. Key takeaways:

  • Namespaces help avoid naming conflicts and organize code logically.
  • The #include directive allows reusing code from other files and libraries.
  • The scope resolution operator :: is used to access elements in a namespace.
  • Shadowed variables occur when an identifier in an inner scope has the same name as an identifier in an outer scope.
  • The standard library provides a rich set of pre-built functionality.
  • std::cout and std::endl are used for console output.
  • using statements allow using names from a namespace without explicit qualification.
  • Literals for std::string are created using the s suffix - for example: "Hello World"s.

Was this lesson useful?

Next Lesson

Conditionals and Loops

Learn the fundamentals of controlling program flow in C++ using if statements, for loops, while loops, continue, and break
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
Lesson Contents

Namespaces, Includes, and the Standard Library

A quick introduction to namespaces in C++, alongside the standard library and how we can access it

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Whirlwind Tour of C++ Basics
  • 53.GPUs and Rasterization
  • 54.SDL Renderers
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 55 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Conditionals and Loops

Learn the fundamentals of controlling program flow in C++ using if statements, for loops, while loops, continue, and break
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved