Tip of the Week #186: Prefer to Put Functions in the Unnamed Namespace

Originally posted as TotW #186 on November 5, 2020

By James Dennett and Jason Rennie

Updated 2020-11-05

Quicklink: abseil.io/tips/186

“Everything should be made as simple as possible, but no simpler.” ~ Roger Sessions’s interpretation of Einstein

When adding a new function, default to making it a non-member function local to the .cc file where it is called. While there are valid reasons to make another choice, consider writing it in an unnamed namespace (also known as an “anonymous namespace”).

Benefits

Writing a non-member in an unnamed namespace has benefits both by making functions internal to a .cc file (moving them out of header files) as well as by making them non-members (moving them out of classes).

Benefits over functions declared in a header file include:

  • Making it easy for a reader to look up the definition (since it’s in the same file as usage and above the first usage).
  • Placing the documentation, declaration, and definition in a single location (versus two locations for functions which are declared in a header file).
  • Isolating it from other source files, making it easier to refactor.
  • Removing the need to worry about meaningful const as there is no separate declaration.
  • Keeping them in the same place as other implementation helpers for a library, such as convenience aliases and local types. See Tip of the Week #119: Using-declarations and Namespace Aliases.
  • Providing side benefits, such as allowing a type to also be moved to an unnamed namespace (if it is only referenced in a single source file).

Benefits over private methods include:

  • Inputs and outputs are clear since they are (much more likely to be) specified via arguments or the return value. Note that a method may read any member variable and a non-const method may modify any non-const member. Conversely, a non-member function may only read or modify according to its interface (except for globals).
  • The class API is simpler and shorter, and hence easier to read—unnecessary private methods may make it difficult to find inheritance-related private declarations or declarations after the class.

Reasons to Look Elsewhere

Sometimes a non-member local function does not make sense. For example:

  • When the function is useful in multiple source files. Declaring it in a header file allows re-use.
  • When the function has complex interactions with an object or class. For example, a function that reads from multiple fields and needs to modify state in a way that can’t be handled naturally via a return value is likely better written as a method. In particular, logic involving a mutex usually belongs in a member function.
  • When the function belongs as part of an class’s API.

An Alternative: static Non-Member Functions

Marking a non-member function as static has essentially the same effect as placing it inside an unnamed namespace in terms of isolating it from code in other translation units. While unnamed namespaces do this in a uniform manner that covers types as well as functions and objects, some people like to see static written explicitly in the declaration of a function to show that it is local to a translation unit without having to check for an enclosing unnamed namespace. While this tip recommends using an unnamed namespace, using static can be a reasonable choice.

Other References

Parts of the style guide point us in this direction, but they don’t go as far. For example:

  • The unnamed namespaces section encourages that definitions go in an unnamed namespace (or be declared static), but doesn’t talk about private methods.
  • The inputs and outputs section encourages using return values, but doesn’t address modification of members (via this); this is essentially an uber-input/output parameter.
  • The local variables section encourages minimizing variable scope, but doesn’t extend the idea to classes and objects.

Summary

File-local functions simplify dependencies and improve locality. Non-member functions increase encapsulation, simplify class definitions, and make dependencies more explicit. When writing a function, consider making it a file-local non-member function, such as by putting it in an unnamed namespace of a .cc file.


Subscribe to the Abseil Blog