Preventing Name Collisions with Namespaces

Problem

You have names from unrelated modules that are clashing, or you want to avoid such clashes by creating logical groups of code in advance.

Solution

Use namespaces to modularize code. With namespaces, you can group large groups of code in separate files into a single namespace. You can nest namespaces as deeply as necessary to partition a large module into submodules, and consumers of your module can selectively expose the elements in your namespace they want to use. Example 2-5 shows a few of the ways you can use a namespace.

Example 2-5. Using namespaces

// Devices.h #ifndef DEVICES_H_ _ #define DEVICES_H_ _ #include #include namespace hardware { class Device { public: Device( ) : uptime_(0), status_("unknown") {} unsigned long getUptime( ) const; std::string getStatus( ) const; void reset( ); private: unsigned long uptime_; std::string status_; }; class DeviceMgr { public: void getDeviceIds(std::list& ids) const; Device getDevice(const std::string& id) const; // Other stuff... }; } #endif // DEVICES_H_ _ // Devices.cpp #include "Devices.h" #include #include namespace hardware { using std::string; using std::list; unsigned long Device::getUptime( ) const { return(uptime_); } string Device::getStatus( ) const { return(status_); } void DeviceMgr::getDeviceIds(list& ids) const { } Device DeviceMgr::getDevice(const string& id) const { Device d; return(d); } } // DeviceWidget.h #ifndef DEVICEWIDGET_H_ _ #define DEVICEWIDGET_H_ _ #include "Devices.h" namespace ui { class Widget { /* ... */ }; class DeviceWidget : public Widget { public: DeviceWidget(const hardware::Device& dev) : device_(dev) {} // Some other stuff protected: hardware::Device device_; }; } #endif // DEVICEWIDGET_H_ _ // main.cpp #include #include "DeviceWidget.h" #include "Devices.h" int main( ) { hardware::Device d; ui::DeviceWidget myWidget(d); // ... }

 

Discussion

Example 2-5 is a bit complicated, but let's go through it piece by piece, because it illustrates several key points about namespaces. Imagine that you are writing an administrative application that needs to interface with a bunch of hardware devices. You might want to divide your application into two or more namespaces to prevent naming collisions, or just to divide logically the two parts of the application in a way that makes sense.

First, consider the file Devices.h. It contains a couple of classes that manage the hardware elements, Device and DeviceMgr. I don't want them in the global namespace though (meaning their names are visible everywhere else in the program), so I put them in the hardware namespace:

#ifndef DEVICES_H_ _ // See Recipe 2.0 #define DEVICES_H_ _ #include #include namespace hardware { class Device { // ... }; class DeviceMgr { // ... }; } #endif // DEVICES_H_ _

The mechanism is simple: wrap everything you want to put in your namespace in a namespace block.

The above excerpt is the declaration of Device and DeviceMgr, but we still have to think about the implementation, which is in Devices.cpp. Once again, wrap everything in a namespace blockit is added to what's already in that namespace:

#include "Devices.h" #include #include namespace hardware { using std::string; using std::list; // Implementation for Device and DeviceMgr }

At this point, the hardware namespace contains everything we need it to. All that's left is to use it from somewhere. There are a few ways to do this; the way I did it in Example 2-5 is to qualify fully the name to the Device class with the namespace name, like this:

#ifndef DEVICEWIDGET_H_ _ #define DEVICEWIDGET_H_ _ #include "Devices.h" namespace ui { class Widget { /* ... */ }; class DeviceWidget : public Widget { public: DeviceWidget(const hardware::Device& dev) : device_(dev) {} // Other stuff... protected: hardware::Device device_; }; } #endif // DEVICEWIDGET_H_ _

I also did the same thing from main in main.cpp:

int main( ) { hardware::Device d; ui::DeviceWidget myWidget(d); }

To add types to one of the namespaces, declare your header and implementation files in the same way as in Example 2-5. Each time you wrap code in a namespace block, it is added to that namespace, so you can have code that's in the same namespace that doesn't have to know anything about the other code in that namespace.

If you use the approach of qualifying class names with their namespace, you will quickly get tired of all the typing. There are a couple of ways to make this problem go away. You can create an alias for a namespace-qualified type with the using keyword:

using hardware::Device; int main( ) { Device d; // No namespace name needed ui::DeviceWidget myWidget(d); }

In subsequent code, you can simply refer to the alias instead of the entire namespace name. Or, you can import everything in the namespace by using the namespace instead of one of the types it contains:

using namespace hardware; int main( ) { Device d; ui::DeviceWidget myWidget(d); }

You have probably already used this, or at least seen it in examples, when using the standard library (many of the examples in this book use this technique). Everything in the standard library is in the std namespace, so quite often, you will see this:

using namespace std;

Importing an entire namespace is often a bad idea, though, and is generally considered a bad practice. We have imported the std namespace in most of the examples in this book for clarity only, and, in general, recommend against doing so in real programs.

If you are importing an entire namespace, or several of them, the utility of namespaces decreases significantly. One of the reasons namespaces exist is to reduce naming collisions. If you import a bunch of different namespaces, then you increase the probability of naming conflicts. Your code may compile fine and run now, but someone, somewhere else, can add something to the namespace in the future and create a conflict the next time your code is rebuilt.

You can also nest namespaces to divide the contents of a namespace into smaller groups. For example, the hardware namespace I defined in Example 2-5 might actually contain a lot of network classes and more device classes, so I could partition the namespace by nesting some others with more descriptive names:

namespace hardware { namespace net { // network stuff } namespace devices { // device stuff } }

Now, I can access elements contained in the namespace with a bit more qualification:

// In some other file... using hardware::devices::Device;

Namespaces are handy. There are a few cool things that you can do with namespaces to make your life easier: namespace aliases, automatic name lookup in function parameter namespaces, and name matching for function overloads in using declarations. The last two are wordy, but simple.

A namespace alias is just what it sounds like: a (probably short) name that you can substitute for another (probably long) namespace name. If you don't want to use a using statement, but also don't want to type out a huge fully qualified name every time you use a class, you can create an alias for it:

using dev = hardware::devices; // ... dev::Device d;

You can then use the alias when referring to elements in that namespace.

C++ also provides automatic lookup in function parameter namespaces. So, for example, the following code qualifies its argument with a namespace (dev is the namespace that Device is declared in):

void f(dev::Device& d) { register(d); // This is actually dev::register }

When you pass in a function parameter that belongs to a namespace, the compiler includes that namespace when performing name lookup on functions within the body of the function. You may not need this every day, but it saves a lot of typing or an extra using directive when you do. The idea behind this is that functions that operate on some type are often defined in the same namespace as that type. Incidentally, it a good practice, in general, to put functions that operate on certain types in the same namespace as those types, when possible.

The last cool thing about namespaces is name matching for overloads in using declarations. Consider this example:

namespace mylib { void foo(int); void foo(double); void foo(std::string); // More overloads of foo( )... } // In some other file... using mylib::foo; // Which one does this use?

The using declaration matches all overloads of foo so you don't have to write one for each overload. The other benefit of this is that if an overload of foo is added, any code with a declaration such as using mylib::foo "sees" it automatically because the using declaration will pick it up (when the code containing the using declaration is compiled, of course).

You have to use namespaces wisely, though, or you may get some unexpected compilation errors or create them for others who use your code. Here are a few popular guidelines when using namespaces:

 

Use using namespace xxx sparingly

As I explained earlier, importing an entire namespace increases the probability of a name collision, either right now or in the future (someone may add to the namespace you are using, creating a conflict in your code). It also dilutes the modularity provided by namespaces.

 

Don't use using statements in header files

Header files are included by lots of other files, so if you use a namespace or something in a namespace in a header file, you expose what you are using to the file that is including your header file. The solution to this problem is to qualify fully everything you need in a header file.

 

Don't put using declarations or definitions before #include directives

If you do this, then you expose whatever you're using to the code in the header file, which is probably not what the author of the header file intended.

If you obey these guidelines, using namespaces in a new project or adding them to an existing project should be relatively easy.

Категории