5

I'm familiar with the notion in c++ of the rule of 3, however since the release of C++11 I've seen some sources suggesting it should be extended to a "rule of 5", I.e. the move constructor and move assignment operator should also be implemented whenever the others are. What is the rationale behind such a rule? My understanding is that in most cases implementation of move semantics is only necessary as an optimization - am I wrong about this, or is the so-called rule of five about optimizing my code (and, therefore, substantially less important than the rule of 3, which is about avoiding pitfalls that can lead to unexpected behaviors)?

5

Move semantics were added to C++11 in such a way that the "rule of 3" is still valid and sufficient to avoid the pitfalls that it is meant to avoid (althogh there are better ways in most situations by applying the "rule of 0").
There are also no additional pitfalls added to the language that the "rule of 5" would help you avoid. In that sense, the rule of 5 is more about optimization than about avoiding pitfalls.

On the other hand, treating the "rule of 5" as an extension to the "rule of 3" does help you to remember that in C++11 there are two additional special member functions that you need to think about when your class does something special that needs a destructor or copy-constructor.

And although move semantics are an optimization, providing a move-constructor and/or move-assignment operator doesn't mean you are prematurely optimising, just like selecting the right algorithm isn't a premature optimization.
Adding support for move semantics to your class, when it makes sense, usually takes little effort and doesn't detract from the readability/maintainability of the code.

  • I downvoted you principally because your advice would entail implementing three to five redundant operations per class. – DeadMG Dec 26 '14 at 13:52
  • @DeadMG: That only shows you don't understand the "rule of three". In what way am I recommending to implement redundant operations? – Bart van Ingen Schenau Dec 26 '14 at 15:15
  • The three, or five. Both of those rules are made obsolete by unique_ptr (five didn't even live long enough to become a real rule) and involve implementing copy and move operations for absolutely no benefit at all that just make the code less reliable and maintainable. I understand the rule of three just fine- it just only applies to C++03 and not C++11. – DeadMG Dec 26 '14 at 16:42
  • @DeadMG: I agree that std::unique_ptr reduces the number of times that you need a custom copy/move-constructor, but I don't agree that it reduces the need to 0. As long as that need is non-zero, there is a use for the rule of three/five. – Bart van Ingen Schenau Dec 26 '14 at 17:33
  • 1
    @DeadMG: Even in C++03/C++98 the "rule of three" is only applicable to a minority of all classes that one would write. As the question was about the difference between the rule of three and the rule of five, I didn't see the need for warnings that apply equally to both and have applied since C++98. std::uniqu_ptr didn't change anything there. – Bart van Ingen Schenau Dec 26 '14 at 20:25
5

The Rule of Five is ex-idiomatic. It was only idiomatic for a very brief period before the Rule of Zero.

The principle of the Rule of 3 became obsolete when writing your own resource-handling classes became obsolete, which is when your compiler supports rvalue references. If you implement in terms of unique_ptr, which you can for virtually every resource because the deleter is so customizable, you only need a custom copy assignment and copy constructor- so two. If you need to support copying. There's no reason to implement your own destructor, move assignment operator, or move constructor in the vast majority of cases.

Rule of Three code isn't broken but it's much harder to maintain than Rule of Zero code and not as efficient either. Move operations offer correctness in many cases like unique_ptr so you should strive to support them wherever possible.

It's copy operations that are now largely redundant in the face of moves, not the other way around.

In other words, unique_ptr is so flexible, and defaulted operations so useful, that there's hardly ever any reason to implement your own special members anymore. In fact, there's hardly any reason to implement your own resource handling classes. And since those classes virtually never exist, there's no reason to to have an idiom to construct them.

  • This is an interesting response, and I can certainly see the attraction of the rule of zero, but I do a lot of interacting with os resources in my code and prefer to encapsulate them in a class with a destructor rather than relying on client code to configure an appropriate delete operation. – Jules Dec 26 '14 at 16:02
  • @Jules: Except the Standard already provides one class that can handle every OS resource in that exact fashion- std::unique_ptr. Even if you wrap it in your own class, you can still just default the members instead of needing to implement them yourself. – DeadMG Dec 26 '14 at 16:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.