Namespaces in C++ (ENG)

1. Family Name in C++


People have family names to help distinguish individuals who have the exact same first name. This is a worldwide practice, no matter where you live. In C++, we do the same thing. We create specific namespaces to avoid name conflicts.

Using the Namespace

To the most of us, the very first encounter with namespace in C++ would be likely using namespace std; as shown in the example below:

#include <iostream>
using namespace std;

int main(){
	cout << "Hello world!" << endl;
	return 0;
}

Using using namespace std; globally allows us to avoid repeatedly typing the std:: prefix. However, this practice can be harmful when handling large projects. Why?

Preventing Name Conflicts

As we just discussed, namespaces in C++ help us avoid name conflicts. Imagine this scenario: you're using multiple libraries that define functions with the same name. When you call these functions, how can the compiler (or linker) know which function to use? Just like if there are two people named Henry in your class, you don't just call out "Henry" because both might respond.

Since this would cause a loading error, we must have some mechanism to solve this problem. In C++, this mechanism is the namespace. It's like giving a function a name suffix. Let me give an easy example:

int sum(int a, int b) {
    return a + b;
}
namespace congzhi{ // namespace definition
    int sum(int a, int b) {
    // Do something else...
        return a + b;
    }
}
int main() {
    sum(1, 2);
    {
		using namespace congzhi;
		sum(1,2); // call congzhi::sum(1,2);
    }
//  congzhi::sum(1,2); // Same
    return 0;
}

Let's say we use a math library that has a sum() function, but we would like our own sum() function to do something else inside the function scope. What we do is add a suffix name to the function using a namespace.

After compiling, you would see the difference: the congzhi namespace is actually added before the function name, resulting in _ZN7congzhi3sumEii, exactly like a suffix name or family name if you will.

_Z3sumii:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	ret
_ZN7congzhi3sumEii:
	pushq	%rbp
	movq	%rsp, %rbp
	movl	%edi, -4(%rbp)
	movl	%esi, -8(%rbp)
	movl	-4(%rbp), %edx
	movl	-8(%rbp), %eax
	addl	%edx, %eax
	popq	%rbp
	ret
main:
	pushq	%rbp
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	$2, %esi
	movl	$1, %edi
	call	_Z3sumii
	movl	$2, %esi
	movl	$1, %edi
	call	_ZN7congzhi3sumEii
	movl	$0, %eax
	popq	%rbp
	ret

By the way, :: is called the scope resolution operator in C++, and it is used to specify which namespace or class a particular function or variable belongs to. The :: operator is also used to refer to the global namespace. As in:

int func(){ // global function
}
int var; // global variable

int main(){
	::var = 10; // equals to var = 10;
	::func(); // equals to func();
}

Class as a Suffix Also

Just like namespaces, classes can also play a similar role in C++. By defining member functions within a class, you can ensure that functions with the same name do not conflict with each other.

class MathLib {
public:
    int sum(int a, int b) {
        return a + b;
    }
    inline static int i = 0;
};

int main() {
    MathLib math;
    int result1 = math.sum(1, 2); // Calls MathLib::sum
    MathLib::i = 5; // Accesses static variable i
    return 0;
}

Differently, unlike namespaces, classes provide encapsulation for member variables and member functions. This is something you cannot achieve with namespaces. That's why in most large projects, we use this pattern:

namespace congzhi{
	class MathLib{};
	class GraphLib{};
	class IOLib{};
}

Thus we can group those related functionalities and avoid name conflicts.

Nested Namespaces

Nested namespaces are not recommended in good design in most cases. We use namespaces primarily to distinguish different libraries. For example, std is used for the C++ standard library, and sdl is used for the SDL library. However, if you wish, you can add suffixes to function names as much as you like.

You simply use nested namespaces like this:

// nested namespaces
namespace A{ // Equals to: namespace A::B::C{ }
	namespace B{
		namespace C{
			exampleFunc(){
				// ...
			}
			// Some functions here...
		}
	}
}
int main(){
	{ // using derectives
		using namespace A::B::C;
		exampleFunc();
	} // A using namespace scope with life time
	A::B::C::exampleFunc(); // Same
	return 0;
}

Use using namespace as Sparingly as Possible

No denying, using the using namespace directive can be convenient. However, it can also bring issues that are not easy to identify, especially as the project grows larger.

// lib.hpp
namespace A{
	int sum(int a, int b){ // A::sum()
		return a + b;
	}
}
namespace B{
	int sum(int a, int b){ // B::sum()
		return a + b;
	}
}
// main.cpp
#include "lib.hpp"
using namespace A; // gloabl namespace
using namespace B; // global namespace in another file
int main(){

	sum(1, 2); // which sum() would it call?
}

In this simple example, try to guess which function it would call. You might think, "Well, I could just use the scope resolution operator B:: in this case, right?" But in a large project built by a team of people, this sum() call becomes ambiguous. Which one does it call? Even the compiler won't know, leading to compilation errors and potential big problems in the future.

Warning!!!

NEVER EVER WRITE using namespace AT GLOABL SCOPE IN A HEADER FILE

So a good practice is as follows:

// main.cpp
#include "lib.hpp"

// No global namespace directives are allowed.

int function_using_lib_a(int a, int b) {
    using namespace A;
    return sum(a, b); // Calls A::sum
}
int main() {
    function_using_lib_a(1, 2);
    { // Samll scope
        using namespace B;
        sum(1, 2); // Calls B::sum
    }
    
    // Or like this:
    B::sum(1, 2); // Calls B::sum, even cleaner...
    return 0;
}

Make the scope of using namespace as small as possible, or try not to use it at all.

2. Namespace Aliases


Remember we had a example look like this? Every time we wanted to use exampleFunc(), we had to add the long suffix A::B::C::, this can be annoying. So, we can use namespace alias to define a alternate name for a namespace.

// nested namespaces
namespace A{ // Equals to: namespace A::B::C{ }
	namespace B{
		namespace C{
			exampleFunc(){
				// ...
			}
			// Some functions here...
		}
	}
}

namespace ABC = A::B::C; // namespace alias

int main(){
	A::B::C::exampleFunc();
	ABC::exampleFunc(); // Same
	return 0;
}