Why Is My Object Not Being Copied When Move Constructor Is Deleted

Why is my object not being copied when move constructor is deleted?

Deleted move members are evil. They are not outlawed, because some day, someone will find a clever use for them. But I haven't seen a good use yet.

Deleting a special member is not the same thing as not having a special member. Nowhere is this more obvious than with the move constructor and move assignment operator.

When present, whether deleted, defaulted, or user-defined, the move constructor and the move assignment operator participate in overload resolution. That means that they "compete" with the special copy members. The copy members will typically favor const lvalues, whereas the move members attract rvalues.

When returning a local type from a function (when the local type is the same un-cv-qualified type as the return type), the return statement first considers the return expression as an rvalue, and only if it can't find a suitable constructor, will it then be considered as an lvalue. I.e. matching the proper constructor for returning a local object from a function is a 2-phase operation.

When you don't have a move constructor at all (not even deleted), but you do have a normal copy constructor (takes a const &), then the rvalue from the return statement will match the copy constructor.

When you do have a move constructor, even it is marked deleted, the rvalue from the return statement will find the move constructor a better match than the copy constructor.

Summary

Unless you really know what you are doing, never delete the move members. If you don't want your type to be movable, just do not define the move members and make sure that you do declare copy members, even if the copy members are =default'd.

Update

I suppose it's hard to provide quotes from the Standard on what delete
doesn't do? – DyP

8.4.3 Deleted definitions [dcl.fct.def.delete]

2 A program that refers to a deleted function implicitly or
explicitly, other than to declare it, is ill-formed. [ Note: This
includes calling the function implicitly or explicitly and forming a
pointer or pointer-to-member to the function. It applies even for
references in expressions that are not potentially-evaluated. If a
function is overloaded, it is referenced only if the function is
selected by overload resolution. — end note ]

Update 2

12.8 Copying and moving class objects [class.copy]

9 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, and
  • X does not have a user-declared destructor.

[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 ]

Why can an object with deleted copy- and move-constructor still be passed to a function accepting an r-value reference?

rvalue references are just references just like lvalue references. The difference between them is the kind of expression that can be used to initialize them.

std::move casts an lvalue reference to an rvalue reference. It doesn't require the type to be movable. If you try to actually move thing in something then you will get errors.

Class with a deleted copy constructor can be still copied?

In foo({ });, the parameter is copy list-initialized. As the result it's initialized by the default constructor of std::atomic.

In foo(atomic<int>{ });, the parameter is copy-initialized from the temporary std::atomic (i.e. atomic<int>{ }). Before C++17 even the copy/move operation might be elided the copy/move constructor still has to be present and accessible. Since C++17 this is not required again because of mandatory copy elision, it's guaranteed that the parameter is initialized by the default constructor of std::atomic directly.

First, if T is a class type and the initializer is a prvalue expression whose cv-unqualified type is the same class as T, the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object: see copy elision (since C++17)

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible: (since C++17)

Why can I move an object with deleted move constructor and assignment operator?

capture_out() takes an rvalue reference as its parameter, but it is still just a reference nonetheless. std::move() is just a typecast to a reference, it doesn't actually move anything. Your example constructs only one FileDescriptor object and then passes it by reference to capture_out(), there is no second object being constructed or assigned to, so your deleted constructors and deleted operators are not called. Nothing is actually being copied or moved.

why does deleting move constructor cause vector to stop working

Summary

Don't delete the move members.


Assuming your compiler is completely C++11 conforming, then explicitly deleting the move constructor will also implicitly declare the following:

Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;

That is if you declare a move constructor (or move assignment operator), and do not declare copy members, they are implicitly declared as deleted. So your complete class Foo is as if:

class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(Foo&&) = delete;
Foo(const Foo&) = delete; // implicitly declared
Foo& operator=(const Foo&) = delete; // implicitly declared

int i_;
};

Now vector<Foo>::push_back(Foo(1)) requires that Foo be MoveConstructible. MoveConstructible could be satisfied by an accessible move constructor, or even by an accessible copy constructor. But Foo has neither. To fix you could:

class Foo
{
public:
Foo(int i) : i_(i) {}
Foo(const Foo&) = default;
Foo& operator=(const Foo&) = default;

int i_;
};

I.e. default the copy members and remove the deleted move member.

In general it is not a good idea to explicitly delete the move members. If you want a class to be copyable but not "movable", just declare exactly as you would in C++03: declare/define your copy members. You can let the copy members be compiler-generated with = default, and that still counts as a user-declaration. And don't declare move members. Move members that don't exist are not the same as deleted move members.

Deleted move members mean you can not construct a copy of Foo from an rvalue, even if the copy constructor would have worked fine to do so. This is rarely the desired intent.

Even if you want your class to not be copyable nor movable, it is better to just delete the copy members and leave the move members undeclared (meaning they won't exist). If you're ever reviewing code (including your own), and see deleted move members, they are almost certainly incorrect, or at the very best superfluous and confusing.

Some day someone will come up with a good use case for deleted move members. But it will be a rare use case. If you see such a pattern in code, you should expect the code author to have a very good explanation. Otherwise, deleted move members are likely to just be incorrect (at best superfluous). But on the bright side this error will show itself at compile time, instead of at run time (as in your example).

Here is a summary chart of what the compiler will implicitly do when you explicitly declare any of the special members. Those squares colored red represent deprecated behavior.

Sample Image

= default and = delete count as user-declared.

Click here if you would like to view the full slide deck.

Why is the move constructor not invoked when returning an rvalue?

Case 1

Here we consider the statement:

 Animal a1 = func1();

The call expression func1() is an rvlalue of type Animal. And from C++17 onwards, due to mandatory copy elison:

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible:

  • In a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:

That is, the object is constructed directly into the storage where they would otherwise be copied/moved to. That is, in this case(for C++17), there is no need of a copy/move constructor to be available. And so this statement works.

Case 2

Here we consider the statement:

Animal a2 = func2();

Here from non mandatory copy elison,

Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:

  • In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type.

That is, the copy/move constructors are required to exist(that is these ctors must be present and accessible) but since you've explicitly marked them as deleted this statement fails with the error:

error: use of deleted function ‘Animal::Animal(Animal&&)’

The error can also be seen here



Related Topics



Leave a reply



Submit