Why does the compiler require a copying constructor, need and have moving one and doesn't uses any of them?

前端 未结 3 845
醉酒成梦
醉酒成梦 2021-01-07 03:26

I\'ve already tried to ask this question but I wasn\'t clear enough. So here is one more try. And I am very sorry for my English ;)

Let\'s see the code:

         


        
相关标签:
3条回答
  • 2021-01-07 03:51

    This is called copy elision.

    The rule in this situation is that a copy/move operation is specified, but the compiler is allowed to optionally elide it as an optimization, even if the copy/move constructor had side-effects.

    When copy elision happens, typically the object is created directly in the memory space of the destination; instead of creating a new object and then copy/moving it over to the destination and deleting the first object.

    The copy/move constructor still has to actually be present, otherwise we would end up with stupid situations where the code appears to compile, but then fails to compile later when the compiler decides not to do copy-elision. Or the code would work on some compilers and break on other compilers, or if you used different compiler switches.


    In your first example you do not declare a copy nor a move constructor. This means that it gets an implicitly-defined copy-constructor.

    However, there is a rule that if a class has a user-defined destructor then it does not get an implicitly-defined move constructor. Don't ask me why this rule exists, but it does (see [class.copy]#9 for reference).

    Now, the exact wording of the standard is important here. In [class.copy]#13 it says:

    A copy/move constructor that is defaulted and not defined as deleted is implicitly defined if it is odr-used (3.2)

    [Note: The copy/move constructor is implicitly defined even if the implementation elided its odr-use (3.2, 12.2). —end note

    The definition of odr-used is quite complicated, but the gist of it is that if you never attempt to copy the object then it will not try to generate the implicitly-defined copy constructor (and likewise for moving and move).

    As T.C. explains on your previous thread though, the act of doing A a[2] = {0, 1}; does specify a copy/move, i.e. the value a[0] must be initialized either by copy or by move, from a temporary A(0). This temporary is able to undergo copy elision, but as I explain earlier, the right constructors must still exist so that the code would work if the compiler decides not to use copy elision in this case.

    Since your class does not have a move constructor here, it cannot be moved. But the attempt to bind the temporary to a constructor of A still succeeds because there is a copy-constructor defined (albeit implicitly-defined). At that point, odr-use happens and it attempts to generate the copy-constructor and fails due to the unique_ptr.


    In your second example, you provide a move-constructor but no copy-constructor. There is still an implicitly-declared copy-constructor which is not generated until it is odr-used, as before.

    But the rules of overload resolution say that if a copy and a move are both possible, then the move constructor is used. So it does not odr-use the copy-constructor in this case and everything is fine.

    In the third example, again the move-constructor wins overload resolution so it does not matter what how the copy-constructor is defined.

    0 讨论(0)
  • 2021-01-07 04:03

    I think you are asking why this

    A a[2] = { 0, 1 };
    

    fails to compile, while you would expect it to compile because A may have a move constructor. But it doesn't.

    The reason is that A has a member that is not copyable, so its own copy constructor is deleted, and this counts as a user declared copy constructor has a user-declared destructor.

    This in turn means A has no implicitly declared move constructor. You have to enable move construction, which you can do by defaulting the constructor:

    A(A&&) = default;
    

    To check whether a class is move constructible, you can use is_move_constructible, from the type_traits header:

    std::cout << std::boolalpha;
    std::cout << std::is_move_constructible<A>::value << std::endl;
    

    This outputs false in your case.

    0 讨论(0)
  • The twisted logic is that you are supposed to write programs at a higher abstraction level. If an object has a copy constructor it can be copied, otherwise it cannot. If you tell the compiler this object shall not be copied it will obey you and not cheat. Once you tell it that it can be copied the compiler will try to make the copy as fast as possible, usually by avoiding the copy constructor.

    As for the move constructor: It is an optimization. It tends to be faster to move an object from one place to another than to make an exact copy and destroy the old one. This is what move constructors are for. If there is no move constructor the move can still be done with the old fashioned copy and destroy method.

    0 讨论(0)
提交回复
热议问题