In this article we look at one of the issues inherent in C when building larger projects – the problem of function and object naming. We look at the C++ solution to this: namespaces.
A problem with big projects in C
When we move to multi-file projects the problem in C is having to create unique names for functions and externs in the global namespace. If we don’t have unique definitions this will lead to a link-time error.
We could, of course, make the functions static but then they are only available within the file they are defined in.
A common solution to the problem amongst C programmers is to add an extension to the function name to make it unique. Very commonly this is the module name.
This works for your own code but is often not an option for third-party code:
The C++ answer: namespaces
A namespace is a named scope. A user-defined namespace is a mechanism for expressing logical grouping in your code.
By putting the classes Longitude and Latitude into the namespace Nav we have effectively extended their names by ‘prefixing’ them with the namespace name.
In the implementation file we must prefix the namespace name onto the class (using the scope resolution operator) when we define the member functions (or indeed any other member). An alternative notation is to enclose all the class definitions within a namespace declaration.
Elements defined within a namespace can be accessed in any of three ways:
- by using the fully qualified name, in this case Nav::
- If the item is used a lot, then it can individually be brought into the global namespace with the using directive.
- The global statement using namespace Nav makes all names in the namespace available
Namespaces are an open scope – it is possible to keep adding definitions to the namespace, across different translation units. Although classes act as namespaces they are referred to as a closed scope – that is, once a class (namespace) has been defined it cannot be added to.
It is good practice to put all code into a namespace, rather than leaving it in the global namespace. The only thing that should (ideally) be in the global namespace is main(). (MISRA-C++ makes this demand of you.)
Namespaces may be nested arbitrarily deep. Nested namespaces are analogous to a hierarchical file system, rooted in the global namespace (which is identified by having nothing to the left of the scope resolution operator (::)
If your coding standard demands that you explicitly qualify type names then having hierarchies of namespaces (each with a descriptive name) can quickly become onerous, and lead to less-than-readable code. To improve legibility C++ allows namespace aliasing. A namespace alias is a – usually shorter and more succinct – synonym for the declared namespace.
Forward references and namespaces
Wherever possible we want to reduce the coupling between modules in our design. Including one header file within another builds dependencies (coupling) between the two interfaces.
In this case, including the class definition of class Sensor is unnecessary. You only need to include the class definition if you are going to allocate memory for an object (a Sensor object, in this case) or access any of its member variables or operations. Class Positioner does not instantiate a Sensor object; it merely has a pointer to a Sensor object. Since the compiler knows how much memory to allocate for a pointer we do not need to include the class definition of Sensor. However, we must still declare that class Sensor is a valid type to satisfy the compiler. In this case we do so with a forward reference – actually just a pure declaration that class Sensor exists (somewhere).
However, if we put our Sensor and Actuator classes in a namespace we have a problem. In the case of the Positioner class, above, since we are only declaring pointers to Sensor and Actuator objects it is good practice to use forward references to those classes.
The syntax, as shown below, looks reasonable but doesn’t work.
The compiler takes the forward reference as referring to a nested class; it cannot know IO is a namespace.
The solution is to tell the compiler that IO is a namespace with the namespace keyword. The forward references to Sensor and Actuator can then be declared within the namespace.
Remember from previously, if we want to use a class or function from a namespace we have to explicitly fully-qualify the entity. If this is true (and it is) then the following code shouldn’t compile:
The reason it should fail is the overloaded operator<<. This is actually a function call which, like everything else in the Standard Library, placed in the namespace std
std::ostream& std::operator<< (std::ostream&, const char*);
This means that, in order to access this function, we should have to fully qualify its name:
This is not very readable; and, of course, we know the original code does compile perfectly fine.
The solution is a compiler mechanism called Argument-Dependent Lookup (ADL) or Koenig Lookup (after its inventor, Andrew Koenig). ADL states that if you supply a function argument of class type, then to find the function name the compiler considers matching names in the namespace containing the argument’s type.
In the example above we have defined a class, Digital and an overloaded function doStuff in the namespace Points.
When we make a call to doStuff() with a Points::Digital object the compiler is able to look into the owning namespace of Digital (Points::) and find a function with the correct signature.
However, this only works with arguments of class type so the call to doStuff() with an integer cannot be resolved automatically; the programmer would have to explicitly qualify the function
Our earlier Standard Library example can now be explained: since one of the parameters of std::operator<< is of class type (in this case std::ostream) the compiler can search the std:: namespace for an appropriate function signature without the programmer having to explicitly qualify it. The simplification of library code like this is the primary reason for the inclusion of ADL.
Useful though this is, ADL has the potential to cause us problems in our code. Consider the example below:
Here, the call to doStuff() is ambiguous – it could be either Feabhas::doStuff(Points::Digital&) or Points::doStuff(Points::Digital&) (using ADL). There is no automatic resolution – the programmer must explicitly qualify the call with the appropriate namespace name to get the doStuff() they want.
Preserving the locality of code
In C, the keyword static has two sets of semantics, depending on where it used. The keyword static can be applied to functions and variables
Static functions are not exported, they are private to the module they are defined in. They have internal linkage; they do not appear in the module’s export table. This is useful for preventing your local helper functions from being called outside of your module.
Applying static to objects defined outside any block (confusingly, called ‘static objects’ in the standard!) gives the object internal linkage. The static object is visible anywhere in the translation unit, but not visible from any other translation unit.
When an automatic (local) variable is marked static in a function the compiler allocates permanent storage for it (at compile time). Practically, this means it retains its state between calls to the function but its scope is limited to the function in which it is defined.
C++ extends this behaviour to user-defined (class) types as well.
However, C++ prefers the use of a concept called an un-named namespace instead of static to give objects and functions internal linkage.
An un-named namespace is (as the name suggests!) an anonymous namespace – it does not have a name. The compiler allows entities (objects and functions) in this namespace to be accessed within the defining translation unit, but not outside. Un-named namespaces in different translation units are considered independent (and different). There is no way of naming a member of an unnamed namespace from another translation unit; hence the members of that namespace cannot be accessed (making them, effectively, static).
This removes the need for C’s static, which has now been deprecated (meaning it is currently supported, but likely to be removed from the next revision of the standard.)
Namespaces are a powerful organisational tool in your design. They are a compile-time construct so have no run-time overhead. There’s no good reason not to use namespaces in your code. They will help you build more maintainable, more portable and more reusable code.
For more, even more exciting exploration of this topic have a look here:
- Practice makes perfect, part 3 – Idiomatic kata - February 27, 2020
- Practice makes perfect, part 2– foundation kata - February 13, 2020
- Practice makes perfect, part 1 – Code kata - January 30, 2020
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.
Nice, comprehensive post on the topic.
One little nit on the sentence: "You only need to include the class definition if you are going to allocate memory for an object (a Sensor object, in this case)."
I'd maybe re-word to also include cases where a member is accessed or referenced. I think you caught a little of that a few sentences down when you mentioned calling a member function, but I think it's broader than that.
Nice job on the detail, and more importantly, the examples, in the post. I'll be sharing this with folks on my team.
Thanks Dan. I've updated the wording on use of forward references to make it (hopefully) a little more coherent.