> Is it actually complicated?

It is. There is no point in arguing otherwise.

To understand the problem, you need to understand why it is also a solution to much bigger problems.

C++ started as C with classes, and by design aimed at being perfectly compatible with C. But you want to improve developer experience, and bring to the table major architectural traits such as RAII. This in turn meant you add support for custom constructors, and customize how your instances are copied and destroyed. But you also want to be able to have everything just work out of the box without forcing developers to write boilerplate code. So you come up with the concept of special member functions which are automatically added by the compiler if they are trivial. However, forcing that upon every single situation can cause problems, so you have to come up with a strategy that suits all use cases and prevents serious bugs.

Consequently, you add a bunch of rules which boil down to a) if the class/struct is trivial them compilers simply add trivial definitions of all special member functions s that you don't have to, but once you define any of those special member functions yourself them the compiler steps back and let's you do all the work.

Then C++ introduced move semantics. This refreshes the same problem as before. You need to retain compatibility with C, and you need to avoid boilerplate code, and on top of that you need to support all cases that originated the need for C++'s special member functions. But now you need to support move constructors and move assignment operators. Again, it's fine if the compiler adds those automatically if it's a trivial class/struct, but if the class has custom constructors and destructors then surely you also need to handle moves in a special way, so the compiler steps back and lets you do all the work. On top of that, you add the fact that if you need custom code to copy your objects around, surely you need custom code to move them too, and thus the compiler steps back to let you do all the work.

On top of this, there are also some specific combinations of custom constructors/destructors/copy constructors/copy assignment operators which let the compiler define move constructors/move assignment operators.

It all makes absolutely sense if you are mindful of the design requirements. But if you just start to onboard onto C++ and barely know what a copy constructors is, all these aspects are arcane and sadistic. If you declare nothing then your class instances are copied and moved automatically, but once you add a constructor everything suddenly blows up and your code doesn't even compile anymore. You spotted a bug where an instance of a child class isn't being destroyed properly, and once you add a virtual destructor you suddenly have an unrelated function call throw compiler errors. You add a snazzy copy constructor that's very performant and your performance tests suddenly start to blow up because of the performance hit if suddenly having to copy all instances instead of the compiler simply moving them. How do you sort out this nonsense?

The rule of 5 is a nice rule of thumb to allow developers to have a simple mental model over what they need to do to avoid a long list of issues, but you still have no control over what you're doing. Things work, but work by sheer coincidence.