C++ Programming
Scope
editIn any language, scope (the context; what is the background) has a high impact on a given action or statement validity. The same is true in a programming language.
In a program we may have various constructs, may they be objects, variables or any other such. They come into existence from the point where you declare them (before they are declared they are unknown) and then, at some point, they are destroyed (as we will see there are many reasons to be so) and all are destroyed when your program terminates.
We will see that variables have a finite life-time when your program executes, that the scope of an object or variable is simply that part of a program in which the variable name exists or is visible to the compiler.
Global scope
editThe default scope is defined as global scope, this is commonly used to define and use global variables or other global constructs (classes, structure, functions, etc...), this makes them valid and visible to the compiler at all times.
Local scope
editA local scope relates to the scope created inside a compound statement.
The namespace keyword allows you to create a new scope. The name is optional, and can be omitted to create an unnamed namespace. Once you create a namespace
, you'll have to refer to it explicitly or use the using
keyword. A namespace is defined with a namespace
block.
- Syntax
namespace name {
declaration-list;
}
In many programming languages, a namespace is a context for identifiers. C++ can handle multiple namespaces within the language. By using namespace
(or the using namespace
keyword), one is offered a clean way to aggregate code under a shared label, so as to prevent naming collisions or just to ease recall and use of very specific scopes. There are other "name spaces" besides "namespaces"; this can be confusing.
Name spaces (note the space there), as we will see, go beyond the concept of scope by providing an easy way to differentiate what is being called/used. As we will see, classes are also name spaces, but they are not namespaces.
- Example
namespace foo {
int bar;
}
Within this block, identifiers can be used exactly as they are declared. Outside of this block, the namespace
specifier must be prefixed (that is, it must be qualified). For example, outside of namespace foo
, bar
must be written foo::bar
.
C++ includes another construct which makes this verbosity unnecessary. By adding the line using namespace foo;
to a piece of code, the prefix foo::
is no longer needed.
unnamed namespace
editA namespace
without a name is called an unnamed namespace. For such a namespace
, a unique name will be generated for each translation unit. It is not possible to apply the using
keyword to unnamed namespaces, so an unnamed namespace works as if the using
keyword has been applied to it.
- Syntax
namespace {
declaration-list;
}
namespace alias
editYou can create new names (aliases) for namespaces, including nested namespaces.
- Syntax
namespace identifier = namespace-specifier;
using namespaces
edit- using
using namespace std;
This using-directive indicates that any names used but not declared within the program should be sought in the ‘standard (std)' namespace
.
To make a single name from a namespace
available, the following using-declaration exists:
using foo::bar;
After this declaration, the name bar can be used inside the current namespace
instead of the more verbose version foo::bar. Note that programmers often use the terms declaration and directive interchangeably, despite their technically different meanings.
It is good practice to use the narrow second form (using declaration), because the broad first form (using directive) might make more names available than desired. Example:
namespace foo {
int bar;
double pi;
}
using namespace foo;
int* pi;
pi = &bar; // ambiguity: pi or foo::pi?
In that case the declaration using foo::bar; would have made only foo::bar available, avoiding the clash of pi and foo::pi. This problem (the collision of identically-named variables or functions) is called "namespace pollution" and as a rule should be avoided wherever possible.
using-declarations can appear in a lot of different places. Among them are:
- namespaces (including the default namespace)
- functions
A using-declaration makes the name (or namespace
) available in the scope of the declaration. Example:
namespace foo {
namespace bar {
double pi;
}
using bar::pi;
// bar::pi can be abbreviated as pi
}
// here, pi is no longer an abbreviation. Instead, foo::bar::pi must be used.
Namespaces are hierarchical. Within the hypothetical namespace
food::fruit, the identifier orange refers to food::fruit::orange if it exists, or if not, then food::orange if that exists. If neither exist, orange refers to an identifier in the default namespace
.
Code that is not explicitly declared within a namespace
is considered to be in the default namespace.
Another property of namespaces is that they are open. Once a namespace
is declared, it can be redeclared (reopened) and namespace
members can be added. Example:
namespace foo {
int bar;
}
// ...
namespace foo {
double pi;
}
Namespaces are most often used to avoid naming collisions. Although namespaces are used extensively in recent C++ code, most older code does not use this facility. For example, the entire standard library is defined within namespace
std, and in earlier standards of the language, in the default namespace.
For a long namespace name, a shorter alias can be defined (a namespace
alias declaration). Example:
namespace ultra_cool_library_for_image_processing_version_1_0 {
int foo;
}
namespace improc1 = ultra_cool_library_for_image_processing_version_1_0;
// from here, the above foo can be accessed as improc1::foo
There exists a special namespace
: the unnamed namespace. This namespace
is used for names which are private to a particular source file or other namespace
:
namespace {
int some_private_variable;
}
// can use some_private_variable here
In the surrounding scope, members of an unnamed namespace can be accessed without qualifying, i.e. without prefixing with the namespace name and :: (since the namespace doesn't have a name). If the surrounding scope is a namespace
, members can be treated and accessed as a member of it. However, if the surrounding scope is a file, members cannot be accessed from any other source file, as there is no way to name the file as a scope. An unnamed namespace declaration is semantically equivalent to the following construct
namespace $$$ {
// ...
}
using namespace $$$;
where $$$ is a unique identifier manufactured by the compiler.
As you can nest an unnamed namespace in an ordinary namespace
, and vice versa, you can also nest two unnamed namespaces.
namespace {
namespace {
// ok
}
}
Because of space considerations, we cannot actually show the namespace
command being used properly: it would require a very large program to show it working usefully. However, we can illustrate the concept itself easily.
// Namespaces Program, an example to illustrate the use of namespaces
#include <iostream>
namespace first {
int first1;
int x;
}
namespace second {
int second1;
int x;
}
namespace first {
int first2;
}
int main(){
//first1 = 1;
first::first1 = 1;
using namespace first;
first1 = 1;
x = 1;
second::x = 1;
using namespace second;
//x = 1;
first::x = 1;
second::x = 1;
first2 = 1;
//cout << 'X';
std::cout << 'X';
using namespace std;
cout << 'X';
return 0;
}
We will examine the code moving from the start down to the end of the program, examining fragments of it in turn.
#include <iostream>
This just includes the iostream library so that we can use std::cout to print stuff to the screen.
namespace first {
int first1;
int x;
}
namespace second {
int second1;
int x;
}
namespace first {
int first2;
}
We create a namespace called first and add to it two variables, first1 and x. Then we close it. Then we create a new namespace called second and put two variables in it: second1 and x. Then we re-open the namespace
first and add another variable called first2 to it. A namespace
can be re-opened in this manner as often as desired to add in extra names.
main(){
1 //first1 = 1;
2 first::first1 = 1;
The first line of the main program is commented out because it would cause an error. In order to get at a name from the first namespace
, we must qualify the variable's name with the name of its namespace
before it and two colons; hence the second line of the main program is not a syntax error. The name of the variable is in scope: it just has to be referred to in that particular way before it can be used at this point. This therefore cuts up the list of global names into groups, each group with its own prefixing name.
3 using namespace first;
4 first1 = 1;
5 x = 1;
6 second::x = 1;
The third line of the main program introduces the using namespace command. This commands pulls all the names in the first namespace
into scope. They can then be used in the usual way from there on. Hence the fourth and fifth lines of the program compile without error. In particular, the variable x is available now: in order to address the other variable x in the second namespace
, we would call it second::x as shown in line six. Thus the two variables called x can be separately referred to, as they are on the fifth and sixth lines.
7 using namespace second;
8 //x = 1;
9 first::x = 1;
10 second::x = 1;
We then pull the declarations in the namespace
called second in, again with the using namespace command. The line following is commented out because it is now an error (whereas before it was correct). Since both namespaces have been brought into the global list of names, the variable x is now ambiguous, and needs to be talked about only in the qualified manner illustrated in the ninth and tenth lines.
11 first2 = 1;
The eleventh line of the main program shows that even though first2 was declared in a separate section of the namespace
called first, it has the same status as the other variables in namespace
first. A namespace
can be re-opened as many times as you wish. The usual rules of scoping apply, of course: it is not legal to try to declare the same name twice in the same namespace
.
12 //cout << 'X';
13 std::cout << 'X';
14 using namespace std;
15 cout << 'X';
}
There is a namespace
defined in the computer in special group of files. Its name is std and all the system-supplied names, such as cout, are declared in that namespace
in a number of different files: it is a very large namespace. Note that the #include statement at the very top of the program does not fully bring the namespace
in: the names are there but must still be referred to in qualified form. Line twelve has to be commented out because currently the system-supplied names like cout are not available, except in the qualified form std::cout as can be seen in line thirteen. Thus we need a line like the fourteenth line: after that line is written, all the system-supplied names are available, as illustrated in the last line of the program. At this point we have the names of three namespace
incorporated into the program.
As the example program illustrates, the declarations that are needed are brought in as desired, and the unwanted ones are left out, and can be brought in in a controlled manner using the qualified form with the double colons. This gives the greater control of names needed for large programs. In the example above, we used only the names of variables. However, namespaces also control, equally, the names of procedures and classes, as desired.