Let’s say you are driving to a friend’s house for the first time, and the address given to you is 245 Front Street in Mill City. Upon reaching Mill City, you take out your map, only to discover that Mill City actually has two different Front Streets across town from each other! Which one would you go to? Unless there were some additional clue to help you decide (e.g. you remember your friend’s house is near the river) you’d have to call your friend and ask for more information. Because this would be confusing and inefficient (particularly for your mail carrier), in most countries, all street names and house addresses within a city are required to be unique.
Similarly, C++ requires that all identifiers be non-ambiguous. If two identical identifiers are introduced into the same program in a way that the compiler or linker can’t tell them apart, the compiler or linker will produce an error. This error is generally referred to as a naming collision (or naming conflict).
If the colliding identifiers are introduced into the same file, the result will be a compiler error. If the colliding identifiers are introduced into separate files belonging to the same program, the result will be a linker error.
An example of a naming collision
a.cpp:
main.cpp:
When the compiler compiles this program, it will compile a.cpp and main.cpp independently, and each file will compile with no problems.
However, when the linker executes, it will link all the definitions in a.cpp and main.cpp
together, and discover conflicting definitions for function myFcn(). The linker will then abort
with an error. Note that this error occurs even though myFcn() is never called!
Most naming collisions occur in two cases:
- Two (or more) identically named functions (or global variables) are introduced into separate files belonging to the same program. This will result in a linker error, as shown above.
- Two (or more) identically named functions (or global variables) are introduced into the same file. This will result in a compiler error.
As programs get larger and use more identifiers, the odds of a naming collision being introduced increases significantly. The good news is that C++ provides plenty of mechanisms for avoiding naming collisions. Local scope, which keeps local variables defined inside functions from conflicting with each other, is one such mechanism. But local scope doesn’t work for function names. So how do we keep function names from conflicting with each other?
Scope regions
Back to our address analogy for a moment, having two Front Streets was only problematic because those streets existed within the same city. On the other hand, if you had to deliver mail to two addresses, one at 245 Front Street in Mill City, and another address at 245 Front Street in Jonesville, there would be no confusion about where to go. Put another way, cities provide groupings that allow us to disambiguate addresses that might otherwise conflict with each other.
A scope region is an area of source code where all declared identifiers are considered distinct from names declared in other scopes (much like the cities in our analogy). Two identifiers with the same name can be declared in separate scope regions without causing a naming conflict. However, within a given scope region, all identifiers must be unique, otherwise a naming collision will result.
The body of a function is one example of a scope region. Two identically-named identifiers can be defined in separate functions without issue -- because each function provides a separate scope region, there is no collision. However, if you try to define two identically-named identifiers within the same function, a naming collision will result, and the compiler will complain.
Namespaces
A namespace provides another type of scope region (called namespace scope) that allows you to declare or define names inside of it for the purpose of disambiguation. The names declared in a namespace are isolated from names declared in other scopes, allowing such names to exist without conflict.
Key insight
A name declared within a scope region (such as a namespace) is distinct from any identical name declared in another scope.
For example, two functions with identical declarations can be defined inside different namespaces, and no naming collision or ambiguity will occur.
Namespaces may only contain declarations and definitions (e.g. variables and functions). Executable statements are not allowed unless they are part of a definition (e.g. within a function).
Key insight
A namespace may only contain declarations and definitions. Executable statements are only allowed as part of a definition (e.g. of a function).
Namespaces are often used to group related identifiers in a large project to help ensure they don’t
inadvertently collide with other identifiers. For example, if you put all your math functions in a namespace
named math, then your math functions won’t collide with identically named functions outside the
math namespace.
We’ll talk about how to create your own namespaces in a future lesson.
The global namespace
In C++, any name that is not defined inside a class, function, or a namespace is considered to be part of an implicitly-defined namespace called the global namespace (sometimes also called the global scope).
In the example at the top of the lesson, functions main() and both versions of
myFcn() are defined inside the global namespace. The naming collision encountered in the example
happens because both versions of myFcn() end up inside the global namespace, which violates the
rule that all names in the scope region must be unique.
We discuss the global namespace in more detail in lesson 7.4 -- Introduction to global variables.
For now, there are two things you should know:
- Identifiers declared inside the global scope are in scope from the point of declaration to the end of the file.
- Although variables can be defined in the global namespace, this should generally be avoided (we discuss why in lesson 7.8 -- Why (non-const) global variables are evil).
For example:
The std namespace
When C++ was originally designed, all of the identifiers in the C++ standard library (including std::cin and
std::cout) were available to be used without the std:: prefix (they were part of the global
namespace). However, this meant that any identifier in the standard library could potentially conflict with any
name you picked for your own identifiers (also defined in the global namespace). Code that was once working
might suddenly have a naming conflict when you include a different part of the standard library. Or worse, code
that compiled under one version of C++ might not compile under the next version of C++, as new identifiers
introduced into the standard library could have a naming conflict with already written code. So C++ moved all of
the functionality in the standard library into a namespace named std (short for “standard”).
It turns out that std::cout‘s name isn’t really std::cout. It’s actually just
cout, and std is the name of the namespace that identifier cout is part
of. Because cout is defined in the std namespace, the name cout won’t
conflict with any objects or functions named cout that we create outside of the std
namespace (such as in the global namespace).
Key insight
When you use an identifier that is defined inside a non-global namespace (e.g. the std
namespace), you need to tell the compiler that the identifier lives inside the namespace.”
There are a few different ways to do this.
Explicit namespace qualifier std::
The most straightforward way to tell the compiler that we want to use cout from the
std namespace is by explicitly using the std:: prefix. For example:
The :: symbol is an operator called the scope resolution operator. The identifier to the left
of the :: symbol identifies the namespace that the name to the right of the :: symbol
is contained within. If no identifier to the left of the :: symbol is provided, the global
namespace is assumed.
So when we say std::cout we’re saying “the cout that is declared in namespace
std“.
This is the safest way to use cout, because there’s no ambiguity about which cout
we’re referencing (the one in the std namespace).
Best practice
Use explicit namespace prefixes to access identifiers defined in a namespace.
When an identifier includes a namespace prefix, the identifier is called a qualified name.
Using namespace std (and why to avoid it)
Another way to access identifiers inside a namespace is to use a using-directive statement. Here’s our original “Hello world” program with a using-directive:
A using directive allows us to access the names in a namespace without using a namespace
prefix. So in the above example, when the compiler goes to determine what identifier cout is, it
will match with std::cout, which, because of the using-directive, is accessible as just
cout.
Many texts, tutorials, and even some IDEs recommend or use a using-directive at the top of the program. However, used in this way, this is a bad practice, and highly discouraged.
Consider the following program:
The above program doesn’t compile, because the compiler now can’t tell whether we want the cout
function that we defined, or std::cout.
When using a using-directive in this manner, any identifier we define may conflict with any
identically named identifier in the std namespace. Even worse, while an identifier name may not
conflict today, it may conflict with new identifiers added to the std namespace in future language revisions.
This was the whole point of moving all of the identifiers in the standard library into the std
namespace in the first place!
Warning
Avoid using-directives (such as using namespace std;) at the top of your program or in header
files. They violate the reason why namespaces were added in the first place.
Related content
We talk more about using-declarations and using-directives (and how to use them responsibly) in lesson 7.13 -- Using declarations and using directives.
Curly braces and indented code
In C++, curly braces are often used to delineate a scope region that is nested within another scope region (braces are also used for some non-scope-related purposes, such as list initialization). For example, a function defined inside the global scope region uses curly braces to separate the scope region of the function from the global scope.
In certain cases, identifiers defined outside the curly braces may still be part of the scope defined by the curly braces rather than the surrounding scope -- function parameters are a good example of this.
For example:
The code that exists inside a nested scope region is conventionally indented one level, both for readability and to help indicate that it exists inside a separate scope region.
The #include and function definitions for foo() and main() exist in the
global scope region, so they are not indented. The statements inside each function exist inside the nested scope
region of the function, so they are indented one level.