Lesson 2: Namespace Management
What is a Namespace?
Namespaces are used to organize function, type, and variable
s into logical groups and to prevent name s that can occur especially when your code base includes multiple libraries.Here' we use the term "namespace" simlar to how one would manage Lesson 0's variable proximity. Variable proximity is about being able to see where something is needed by where it is defined. It has the added benefit of keeping identifiers out of the global scope, and into smaller and smaller scopes. Really, a namespace is a named scope that you can choose to pull functions, types, and variables out of.
Global scope
A function is part of the global namespace if it is not explicitly from a specific namespace. You could say it has no namespace, though in some languages they have an explicit global:: tag. It is best practice to avoid using the global scope when possible.
Using namespaces can differentate a given identifier from others with the same name. These organizational structures make your code easier for other people to read/understand.
Objects come with their own namespace.
If you want to tack a function onto an object, just make it static and call it like `Object::function`. You may have been forced to do this when defining an object's function in its source file.
Compiling with multiple files also helps cut down on namespace usage as only identifiers in the
s are referenced in multiple files. In a source file, you can make as much s as you want without worrying that they will collide with other functions in other files.We can abuse this property of classes having namespaces by defining whatever we want. You can define a type, a struct, an enum, an enum class, and even a whole class inside of a class.
But why do that?
I could tell you it's because your code is more organized so it becomes more clean, but the cleanliness of std::string::const_iterator is subjective.
There are two reasons. The first is because thinking of another variable name is hard and wastes time and mental capacity. What am I supposed to call my const iterator, stringConstIterator? The people coding the standard library have made dozens of objects that use these iterators. Keeping that type name in the object's namespace makes it easier for the library writer to write variable names, and easier for the programmer using the library to predict what a type name will be.
In lesson 3, we'll go over object interfaces, so string will implement an iteratable interface, and const_iterator would implement an iterator interface. We can't do that with the type identifier of the iteratable because they'd need to define and purpouse build an object, that string iterator class, inside of the iteratable string.
The second is because you really can't afford to have a collision. Your boss tells you that he needs a rational numbers library, and it needs to have a couple staticl global functions like sqrt, log, pow, etc.
If some user that uses your library does
Even if they don't include a library that dumps everything in the global namespace, users often want to write their own sqrt functions, even properly like in an object's source code file as a helper function. They'd be doing everything right, and still get a compiler message because the library writer did the wrong thing by defining a function in the global namespace.
The function overloading breaking is uncommon, but still possible. For one, you could be writing in a language like C that doesn't have function overloading, but then they probably wouldn't support namespaces. The most common thing is when you're making a rational number, you use something like a long int, and store two single sized ints in it. Then, when you go to call sqrt(myRationalBruh), it calls the wrong sqrt function, treating the whole rational as a single number. Even worse if something else calls sqrt on a long int and gets your rational math.
It is uncommon because overloading requires a different type, you'd need to define your sqrt function using a rational typedef or class. This means as long as you call the function with your rational typedef or class, chances are that the compiler realizes that it's a rational number and uses the correct function, and numbers not declared rational would use the normal sqrt function. Still, if there was no long int sqrt function in cmath, then the compiler might ly cast it to a rational and run your rational sqrt function anyways.
In a perfect world, we wouldn't put any variable in the global namespace in a header. For your own personal projects, it might not be worth the 10 seconds it takes to think of a name for a namespace, but for libraries, you can see how this gets out of hand. That's why std:: and boost:: are a thing.
Nested namespaces
That function overloading problem is only ever a problem if your namespace is too far reaching. As long as you are working in one namespace, you'd need a giant library with a team of programmers, like boost:: or directx in order to accidentally make the same function twice and get an error. Someone in boost or directx might easily write some vector math function twice because of a lack of communication.
Function overloading
Now that function overloading isn't our enemy, let's make it our friend.
When writing properly namespaced functions or public
s, or when writing global scoped helper functions or private helper methods, you can use function overloading to make code look cleaner, and most importantly, save you 10 seconds thinking of another name for your function.The compiler might try to implicitly cast some variables if underlying they use
s, so only use it on primatives you are using as actual primatives, or objects. Objects will also implicitly cast into their parent class. This could be bad if it tries to call a function that is overloaded, as it thinks the new class is the parent class. If you make the object virtual, you can overload functions override members to your heart's content.