The move constructor that you have to declare, even though you don’t want anyone to actually call it

Raymond Chen

Blah blah blah C++ return value optimization (RVO), named return value optimization (NRVO), and copy elision.

RVO support was optional in C++11 but became mandatory in C++17. NRVO support remains optional (but recommended).

To allow NRVO in C++17 (or RVO and NRVO in C++11), a move constructor must be available, even though the compiler will not call it if the optimization is employed.

You may have a class that you want to participate in RVO or NRVO, but you also don’t want it to be moved. For example, it may contain a std::mutex, which is not movable. But you nevertheless have to declare a move constructor. What can you do?

Declare the move constructor, but don’t implement it.

class MyClass
{
public:
  MyClass();

  // Not copyable.
  MyClass(const MyClass&) = delete;

  // Movable only for NRVO purposes (and RVO in C++11).
  // Never implemented.
  MyClass(MyClass&&);

  // Not assignable.
  void operator=(const MyClass&) = delete;
};

MyClass test1()
{
    return MyClass(); // RVO
}

MyClass test2()
{
    MyClass c;
    return c; // NRVO
}

MyClass test3()
{
    MyClass c, d;
    if (some_condition()) {
        return c; // failed NRVO
    } else {
        return d; // failed NRVO
    }
}

We declared a move constructor in order to permit RVO in C++11 and NRVO everywhere. The compiler demands to see a move constructor, even though RVO and NRVO ultimately optimize it out.

The first test shows that RVO works. The second test shows that NRVO works.

The test3 version compiles but does not link: NRVO is not possible given the way that test3 is written, and the compiler is forced to use the move constructor to move either c or d into the return value. We declared the move constructor but never implemented it, so we get an unresolved external, which tells us, “Sorry, this object doesn’t support move, even though the tin says that it does. The label on the tin is a lie and exists only to allow the compiler to use RVO and NRVO.”

4 comments

Discussion is closed. Login to edit/delete existing comments.

  • Sukru Tikves 0

    I would expect the diagnostics message would not be very helpful in this context.

    Actually… let me try…

    Undefined symbols for architecture arm64:
      "MyClass::MyClass(MyClass&&)", referenced from:
          test3() in test-9ef7d2.o
      "MyClass::MyClass()", referenced from:
          test1() in test-9ef7d2.o
          test2() in test-9ef7d2.o
          test3() in test-9ef7d2.o
      "_main", referenced from:
         implicit entry/start for main executable
    ld: symbol(s) not found for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    

    Yes, you need to know what exactly is going on, otherwise one would not recognize failed RVO.

    • Danstur 0

      Damn and that’s clang. I don’t even want to imagine what eldritch horrors gcc or msvc generate.

      C++ error messages are in an awful state and it’s impressive with what we put up with.

  • Pados Károly 0

    > You may have a class that you want to participate in RVO or NRVO, but you also don’t want it to be moved. For example, it may contain a std::mutex, which is not movable. But you nevertheless have to declare a move constructor.

    I think this needs a little correction. RVO since C++17 (e.g. test1 above) does not require a move constructor to be declared. It is only required for NRVO. You actually say this correctly in the paragraph before that:

    > To allow NRVO in C++17 (or RVO and NRVO in C++11), a move constructor must be available, …

    … which then contradicts the next paragraph where you say you need the declaration to participate in “RVO or NRVO”, but in reality is only needed for NRVO (again, since C++17).

    • Pados Károly 0

      Some extra info for the uninitiated: The rules actually have to do with mandatory vs optional copy elision. RVO and NRVO are just specific cases for each, but there are other cases too where copy elision will or could happen. Whether it is mandatory or not dictates if a move constructor must be present.

Feedback usabilla icon