4

While I was learning std::move, I found a strange issue.

If I add only a destructor that do nothing to a perfect program, I will get a compile error.

#include <iostream>
using namespace std;

class M {
public:
  int database = 0;

  M &operator=(M &&other) {
    this->database = other.database;
    other.database = 0;
    return *this;
  }

  M(M &&other) { *this = std::move(other); }

  M(M &m) = default;
  M() = default;
  ~M() { /* free db */ }
};

class B {
public:
  M shouldMove;

  //~B(){}   //<---  ## Adding this line will cause compile error. ##
};

int main() {
  B b;
  B b2 = std::move(b); //## error at this line if the above line is added
  return 0;
}

Live code: https://ideone.com/UTR9ob

The error is invalid initialization of non-const reference of type 'B&' from an rvalue of type 'std::remove_reference<B&>::type {aka B}'.

Question:

  • (1) Which rules of C++ syntax enforce that? In other words, what does the error mean?
  • (2) If I want to add destructor that do almost nothing (e.g. only print debug log) to B, do I really have to follow the rule-of-five instead? If no, how to make it compile? Following the rule of five just because of that is too tedious and dirty in my opinion.

I think the rule of zero is just a good practice.

However, from this example, it seems to me that it is a hard rule that if violated, I will get compile error.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
javaLover
  • 6,347
  • 2
  • 22
  • 67
  • If you add a destructor then you *have* to define some how other special functions if they are going to be used. They can `default` for example `B(B const&) = default` or `B(B&&) = default`. – alfC Dec 06 '16 at 06:37
  • @alfC You mean the answer for the 2nd question is "Yes"? – javaLover Dec 06 '16 at 06:40
  • I think if you write a destructor then you have to *at least* follow the rule of three. http://en.cppreference.com/w/cpp/language/rule_of_three . If you know that you can write a move constructor then you have to follow the rule-of-5. – alfC Dec 06 '16 at 06:47
  • I think the most sane recommendation is delegate all the "non-zero" classes to a base class or an implementation member that only has the special member functions (rule-of-3, rule-of-5, etc). Some times you simply really want a smart pointer to do that. – alfC Dec 06 '16 at 06:49

1 Answers1

6

The implicitly-declared move constructor is only present if a class does not have a user-declared destructor. Therefore the answer to 2. is YES.

The answer to 1. is that this is hard rule and can be found in 12.8, paragraph 9 of the standard:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator,
  • X does not have a user-declared destructor, and
  • the move constructor would not be implicitly defined as deleted.

[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]

The best way of getting this to run,is by using something like a smart pointer, i.e., a base class or member that does define all five special members (and very little else) so that you don't have to. In this case, an integer handle equivalent to std::unique_pointer should work well. However, keep in mind that databases, like files, can have errors while closing, so standard non-throwing destructor semantics don't cover all cases.

Joe
  • 6,497
  • 4
  • 29
  • 55
  • I was just adding it while you wrote the comment :) – Joe Dec 06 '16 at 06:53
  • Nice job, Joe. That is what I looked for, for a long time. Thank. – javaLover Dec 06 '16 at 06:54
  • Good answer, however is the answer to the second question really YES? he can follow the rule of three, no? Specially if he doesn't need a move constructor/assignment. – alfC Dec 06 '16 at 07:10
  • @alfC As the question is explicitly about `std::move` I'd doubt that having the object copied in the line `B b2 = std::move(b);` line would match the intent. – Joe Dec 06 '16 at 07:13
  • @alfC After a fast test, the rule of three is not enough. (It can compile but will just copy.) https://ideone.com/eOawAX It supports Joe's answer. – javaLover Dec 06 '16 at 07:15
  • @javaLover, I think that is because you have a move constructor already. I am confused by your example, you can have a class with destr/copyconstr/copyassign happily. Note that also ` M(M &m) = default;` is something strange to have and doesn't participate in any of the rules (I think.) – alfC Dec 06 '16 at 07:17
  • 1
    @javaLover Yes, if a copy constructor is present your value will be copied, not moved. But it will compile. Remember, `std::move` *does not* move! – Joe Dec 06 '16 at 07:18