- Modern C++:Efficient and Scalable Application Development
- Richard Grimes Marius Bancila
- 1158字
- 2021-06-10 18:27:51
Using Namespaces
Namespaces give you one mechanism to modularize code. A namespace allows you to label your types, functions, and variables with a unique name so that, using the scope resolution operator, you can give a fully qualified name. The advantage is that you know exactly which item will be called. The disadvantage is that using a fully qualified name you are in effect switching off C++'s argument-dependent lookup mechanism for overloaded functions where the compiler will choose the function that has the best fit according to the arguments passed to the function.
Defining a namespace is simple: you decorate the types, functions, and global variables with the namespace keyword and the name you give to it. In the following example, two functions are defined in the utilities namespace:
namespace utilities
{
bool poll_data()
{
// code that returns a bool
}
int get_data()
{
// code that returns an integer
}
}
Do not use semicolon after the closing bracket.
Now when you use these symbols, you need to qualify the name with the namespace:
if (utilities::poll_data())
{
int i = utilities::get_data();
// use i here...
}
The namespace declaration may just declare the functions, in which case the actual functions would have to be defined elsewhere, and you will need to use a qualified name:
namespace utilities
{
// declare the functions
bool poll_data();
int get_data();
}
//define the functions
bool utilities::poll_data()
{
// code that returns a bool
}
int utilities::get_data()
{
// code that returns an integer
}
One use of namespaces is to version your code. The first version of your code may have a side-effect that is not in your functional specification and is technically a bug, but some callers will use it and depend on it. When you update your code to fix the bug, you may decide to allow your callers the option to use the old version so that their code does not break. You can do this with a namespace:
namespace utilities
{
bool poll_data();
int get_data();
namespace V2
{
bool poll_data();
int get_data();
int new_feature();
}
}
Now callers who want a specific version can call the fully qualified names, for example, callers could use utilities::V2::poll_data to use the newer version and utilities::poll_data to use the older version. When an item in a specific namespace calls an item in the same namespace, it does not have to use a qualified name. So, if the new_feature function calls get_data, it will be utilities::V2::get_data that is called. It is important to note that, to declare a nested namespace, you have to do the nesting manually (as shown here); you cannot simply declare a namespace called utilities::V2.
The preceding example has been written so that the first version of the code will call it using the namespace utilities. C++11 provides a facility called an inline namespace that allows you to define a nested namespace, but allows the compiler to treat the items as being in the parent namespace when it performs an argument-dependent lookup:
namespace utilities
{
inline namespace V1
{
bool poll_data();
int get_data();
}
namespace V2
{
bool poll_data();
int get_data();
int new_feature();
}
}
Now to call the first version of get_data, you can use utilities::get_data or utilities::V1::get_data.
Fully qualified names can make the code difficult to read, especially if your code will only use one namespace. To help here you have several options. You can place a using statement to indicate that symbols declared in the specified namespace can be used without a fully qualified name:
using namespace utilities;
int i = get_data();
int j = V2::get_data();
You can still use fully qualified names, but this statement allows you to ease the requirement. Note that a nested namespace is a member of a namespace, so the preceding using statement means that you can call the second version of get_data with either utilities::V2::get_data or V2::get_data. If you use the unqualified name, then it means that you will call utilities::get_data.
A namespace can contain many items, and you may decide that you only want to relax the use of fully qualified names with just a few of them. To do this, use using and give the name of the item:
using std::cout;
using std::endl;
cout << "Hello, World!" << endl;
This code says that, whenever cout is used, it refers to std::cout. You can use using within a function, or you can put it as file scope and make the intention global to the file.
You do not have to declare a namespace in one place, you can declare it over several files. The following could be in a different file to the previous declaration of utilities:
namespace utilities
{
namespace V2
{
void print_data();
}
}
The print_data function is still part of the utilities::V2 namespace.
You can also put an #include in a namespace, in which case the items declared in the header file will now be part of the namespace. The standard library header files that have a prefix of c (for example, cmath, cstdlib, and ctime) give access to the C runtime functions by including the appropriate C header in the std namespace.
The great advantage of a namespace is to be able to define your items with names that may be common, but are hidden from other code that does not know the namespace name of. The namespace means that the items are still available to your code via the fully qualified name. However, this only works if you use a unique namespace name, and the likelihood is that, the longer the namespace name, the more unique it is likely to be. Java developers often name their classes using a URI, and you could decide to do the same thing:
namespace com_packtpub_richard_grimes
{
int get_data();
}
The problem is that the fully qualified name becomes quite long:
int i = com_packtpub_richard_grimes::get_data();
You can get around this issue using an alias:
namespace packtRG = com_packtpub_richard_grimes;
int i = packtRG::get_data();
C++ allows you to define a namespace without a name, an anonymous namespace. As mentioned previously, namespaces allow you to prevent name clashes between code defined in several files. If you intend to use such a name in only one file you could define a unique namespace name. However, this could get tedious if you had to do it for several files. A namespace without a name has the special meaning that it has internal linkage, that is, the items can only be used in the current translation unit, the current file, and not in any other file.
Code that is not declared in a namespace will be a member of the global namespace. You can call the code without a namespace name, but you may want to explicitly indicate that the item is in the global namespace using the scope resolution operator without a namespace name:
int version = 42;
void print_version()
{
std::cout << "Version = " << ::version << std::endl;
}