Friday, December 26, 2014

Movable classes are containers

If you're writing C++11 code, you should be aware that a move constructor and move assignment operator are automatically generated if you write your classes properly. However, there are cases when making a class movable can lead to subtle bugs. This article explains the following:
  1. When we might want to store a pointer to this.
  2. Why storing a pointer to this makes it difficult to implement move operations.
  3. How move operations can be made to work using containers.
  4. Why Movable classes are containers, and why this is a problem.
Note: There exists a definition of the word "container" used by the C++ standard libraries that refers specifically to things like arrays and vectors. In this article, I use the term "container" to refer to any class whose purpose is to contain a pointer to object(s) in one way or other.
Without further ado, let us begin.

 Part 1: If only things were always this simple

Consider the following (simplified) example:
struct Node
{
    Node* Self = this;
};
In this case, it would not be correct to copy an instance of Node because the Self pointer would not be updated to point to the copied instance. There are solutions to this problem: You could disable copying of Nodes entirely (a common choice), or you could implement a custom copy constructor and copy assignment operator that correctly updates the Self pointer. The move operations can be implemented similarly, so in this case there's no real problem. However, as I'll explain in the rest of the article, things get messy when they get more complicated.

Part 2: Where things get ugly

A less obvious incarnation of this problem is when you use the popular C userdata idiom. Consider the following Win32 programming example:
struct Window
{
    HWND NativeHandle;

    Window()
    {
        NativeHandle = CreateWindow(...);

        /* more initialization code goes here */

        SetWindowLongPtr(NativeHandle, GWLP_USERDATA, (LONG_PTR) this);
    }
};
This popular pattern is used to store a pointer to our custom Window class within the Win32 native window handle. This idiom is useful because Win32 events come with the HWND ("Handle to a Window") that emitted them, which we can use to find a pointer to our own Window class. SetWindowLongPtr is the Win32 API function that allows you to specify the pointer to store with the native handle.

You might have noticed that this suffers from the same problem as the simplified example at the start of this article, but it has suddenly become much more difficult to solve the problem. We can no longer implement a hand-written copy constructor because copying a complex opaque resource like a HWND is non-trivial. If we really wanted to, we could actually still implement a move constructor and move assignment operator as long as we are careful to call SetWindowLongPtr to update the handle to point to its new owner. However, this is unwise: According to the MSDN documentation, SetWindowLongPtr is a function that can fail to execute. This is problematic because the design of C++ suggests that move operations should be noexcept. In other words, move operations should never fail.

You could argue that in your system SetWindowLongPtr would never fail in practice, but this is not a general solution to the problem. There must certainly exist similar cases out there where updating the userdata is a non-trivial operation which we can't guarantee will succeed. The only safe solution is to disable move operations entirely, which makes this class much more difficult to use because of how useful move operations are. Thus, in the next part, I will explain how to make it possible to move again.

Part 3: Containers are the solution (and the problem)

The problem in part 2 can be solved, like all other problems in computer science, by another layer of indirection. If we make our Window class a dynamically allocated member of another class, then this new container class can guarantee that the address of our Window instance does not change. It can properly disable copying, and it can safely implement move operations by (for example), swapping the contained pointers to Window instances. Here is an example of such a class:
struct WindowContainer
{
    unique_ptr<Window> ContainedWindow = make_unique<Window>();
};
(Aside: You could skip WindowContainer and just use a plain unique_ptr, but having WindowContainer makes it possible to give it convenient member functions that forward the calls to the contained Window.)

We have now solved the problem of ensuring that the address of our Window instances stay the same (by disabling both copy and move operations), while still making it possible to use move operations by using the WindowContainer instead. However, this has some disadvantages: First, additional dynamic allocations and indirect lookups are required to implement WindowContainer. Second, a pointer to a WindowContainer can't be used to represent the identity of a Window.

That second point is actually pretty annoying. It means that storing a pointer to a WindowContainer is meaningless, because WindowContainers are only used as a vehicle to transport the actual resource (the Window.) In other words, storing a pointer to a WindowContainer is pretty much meaningless because the Window it contains can be moved into a different WindowContainer with a different address. This identity problem actually affects any movable class that contains a resource, which brings me to my final point.

Part 4: Movable classes are containers

If a class is designed to be movable, then a pointer to it can no longer be used to represent the identity of what it contains. This is because its identity can freely be moved into a different object. This problem affects movable classes in general, except perhaps classes where the move operations are implemented simply as a copy (like a POD array.)

It would be a mistake to create a public API that only exposes a single Window class that behaves like WindowContainer, unless you also provide a way to refer to the identity of a Window (which becomes, in my opinion, an unnecessary complication.)

In Conclusion

Because movable classes are disconnected from their identity, it can be concluded that movable classes are containers rather than being resources themselves. Thus, if you create a class that is supposed to represent a resource, it should be neither copyable nor movable. If you want to move it, you should wrap it in a container class like a smart pointer or a standard container. This makes the relationship between container and containee explicit, and makes potential bugs more obvious to users of the API.

Appendix

 I talked a lot about disabling copy and move operations but never showed an example. Here's how you can do such a thing in C++11:
struct NoCopyNoMove
{
    // Disables the copy constructor and copy assignment operator.
    NoCopyNoMove(const NoCopyNoMove&) = delete;
    NoCopyNoMove& operator=(const NoCopyNoMove&) = delete;

    // Disables the move constructor and move assignment operator.
    NoCopyNoMove(NoCopyNoMove&&) = delete;
    NoCopyNoMove& operator=(NoCopyNoMove&&) = delete;
};