This is an in-depth look at the noncopyable class in the C++ boost libraries and the unintended argument dependend lookup (ADL) protection trick.

Note: A better alternative is to delete the copy constructor and assignment, but this article is still useful to understand the mechanics of inheritance and unintended ADL.

Usage

I sometimes use boost::noncopyable to express/ensure that a class cannot/should not be copied. The usage is simple: derive private from it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <boost/core/noncopyable.hpp>

class some_class :
  private boost::noncopyable
{
  // ...
};

void some_fn()
{
  some_class a;
  // line below would fail to compile
  // some_class b = a;
}

Code implementation and analysis

The implementation code for boost::noncopyable (contributed by Dave Abrahams) looks someting like:

1
2
3
4
5
6
7
8
9
10
11
12
namespace boost {
  namespace noncopyable_ {
    class noncopyable {
    protected:
      constexpr noncopyable() = default;
      ~noncopyable() = default;
      noncopyable(const noncopyable &) = delete;
      noncopyable & operator=(const noncopyable &) = delete;
    };
  }
  using noncopyable = noncopyable_::noncopyable;
}

Empty class

Line 3 declares the noncopyable class to derive from. It is an empty class, with no member variables, so in many cases deriving from it will have no size overhead (due to the empty base class optimisation).

Prevent copy

Lines 7 and 8 delete the copy constructor and copy assignment. This prevents a derived class from being copied. It uses the C++ 11 syntax to delete them. Before C++ 11 the copy constructor and copy assignment would have been made private (and only declared, not defined).

Copy and destruction

Lines 4,5 and 6 define the default copy and destructor. They are required because the compiler would stop generating them once we deleted the copy function. It uses the C++ 11 syntax to request the compiler to generate defaults. The access is protected which allows the derived class to access them, but prevents someone directly creating a noncopyable instance (to re-enforce that it’s supposed to be derived from).

The constructor is marked constexpr. Without it one could not have a constexpr instance of the derived class.

Boost namespace

Line 1 and 12. Trivial.

Prevent unintended ADL

Lines 2, 10 and 11. This is for me the most interesting feature of the implementation. The potential problem is caused by ADL.

ADL (of Andrew Koenig fame) allows the code below to work, because in main, the function some_fn is not qualified with a namespace, so some_fn is looked up (and found) in the namespace X for the type Y of it’s argument z.

1
2
3
4
5
6
7
8
9
namespace X {
  struct Y {};
  void some_fn(const Y &) {}
}

int main() {
  X::Y z;
  some_fn(z);
}

Often that’s what we want, and it’s what makes operator<< find the right namespace, based on the arguments provided.

However, the trick is that the lookup is also performed in the namespace of the derived classes for the argument. We would not want a class derived from noncopyable to get involved in ADL against functions in the boost namespace.

To avoid that, noncopyable is not declared in the boost namespace directly, but instead in a namespace called noncopyable_ (notice the extra ending underscore) which has no functions, hence avoids the unintended ADL. The name noncopyable is then reintroduced to the boost namespace with the using on line 11.

Controversies

Taste and style

Which is better? Should one use noncopyable:

1
2
3
4
5
#include <boost/core/noncopyable.hpp>
struct one_class:
  private boost::noncopyable
{
};

or explicitly delete copy constructor an assignment:

1
2
3
4
5
struct two_class
{
  two_class(const two_class &) = delete;
  two_class & operator=(const two_class &) = delete;
};

or rely on side effects of implementing move:

1
2
3
4
5
struct three_class
{
  three_class(three_class &&) = default;
  three_class & operator=(three_class &&) = default;
};

I think people could easily argue either way.

My view is that not all classes are equal. Some are used very few times (e.g. once or twice), others are library classes used a lot.

For library classes, especially if I need to put the effort to implement move as well, then I’ll be explicit about deleting the copy operations, not use noncopyable and not rely on side effects.

But for classes that are used just a few time in my code, I find that the noncopyable approach is easier to read, because it avoids the repetition of the class name.

Optimisation issues

Empty classes still have a sizeof equal to 1, the idea being that it is required so that we can get the address of an instance of that class. The empty class optimisation ensures that an empty base class does not add to the derived class size.

However in a diamond derivation case like below there is an overhead for deriving from the same noncopyable class (compared with just deleting copy constructor and assignment for classes A and B.

1
2
3
4
5
6
7
8
9
10
#include <iostream>

struct A: private boost::noncopyable { };
struct B: private boost::noncopyable { };
struct C: public A, public B { };

int main() {
  // prints 2
  std::cout << sizeof(C) << "\n";
}

The size is 2 in the example above so that there are different addresses for each noncopyable base class.

However in practice this overhead might not be an issue, because if for example class C would have a member of type int of size 4, then the size of C would be just 4, not 6.

References