Why No Default Move-Assignment/Move-Constructor

Why no default move-assignment/move-constructor?

The implicit generation of move constructors and assignment operators has been contentious and there have been major revisions in recent drafts of the C++ Standard, so currently available compilers will likely behave differently with respect to implicit generation.

For more about the history of the issue, see the 2010 WG21 papers list and search for "mov"

The current specification (N3225, from November) states (N3225 12.8/8):

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, and

  • 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.

There is similar language in 12.8/22 specifying when the move assignment operator is implicitly declared as defaulted. You can find the complete list of changes made to support the current specification of implicit move generation in N3203: Tightening the conditions for generating implicit moves
, which was based largely on one of the resolutions proposed by Bjarne Stroustrup's paper N3201: Moving right along.

Why should someone create a default move constructor but delete the move assignment operator?

What this means is that, once an object is created, some aspect of its value cannot later be changed. You can move from it of course, but once that's done, you can't move back to it.

Consider a unique_ptr that had this design. Such a pointer would allow you to transfer ownership, but only to a newly constructed unique_ptr. So basically, if you create a unique_ptr, that pointer object instance will either be managing that specific object, or it will manage nothing, because you moved from it earlier. It will never be managing a different object.

Why isn't the move constructor being called?

Why doesn't Class obj2(obj1.fun()); call the move constructor

Because of Copy Elison. The compiler sees that fun() returns a temporary object, and that temporary will only be used to initialize obj2, so the compiler optimizes the creation of obj2 by eliminating the temporary object altogether and allowing obj2 to be created directly inside of fun() itself, thus there is no need for a copy/move operation when fun() exits.

Class obj2(std::move(obj1.fun())); does call the move constructor

Because you are forcing it with the explicit std::move type-cast, so the compiler can't optimize the creation of obj2 via Copy Elison, so it has to allow fun() to return a temporary object, which you are then moving into the obj2 constructor.

obj3 = obj2.fun() calls the (move) assignment operator

Because obj3 already exists before the assignment.

without needing to write std::move(obj2.fun())

Because fun() returns a temporary object, which is an rvalue, so there is no need to explicitly type-cast it to an rvalue when calling a move assignment operator.

Explicitly defaulted copy/move assignment operators implicitly deleted because field has no copy/move operators. C++

This data member

 const string theSeparators; 

is defined with the qualifier const. So it can not be reassigned after its initialization in a constructor.

Thus the compiler defined the copy and move assignment operators as deleted.

You can just remove the qualifier const for this data member.

Or if to keep the qualifier const for the data member then you have to use mem-initialing lists in constructors like for example

Dictionary::Dictionary(const string& filename, const string& separators = "\t\n")
: filename( filename ), theSeparators( separators )
{
//...
}

But in this case the copy and move assignment operators will be still deleted.

Compiler not generating move constructors

Firstly, obj1 = std::move(obj2); invokes assignment operator, so it has nothing to do with constructors.

Yes, The compiler generates a move assignment operator for A, which perform member-wise move operation, including data member str. The problem is that after move operation str is left in valid, but unspecified state. Also see std::basic_string::operator=.

Replaces the contents with those of str using move semantics. str is in a valid but unspecified state afterwards.

I think you might observe the same result with only std::string, e.g.

std::string str1 = "Init string";
std::string str2 = "Obj2 string";
str1 = std::move(str2);
std::cout << str2;

LIVE with clang, just for reference; it gives the result as you expected but still remember the result is unspecified.

Do move constructors and move assignment operators make sense in classes that don't hold pointers as member variables and don't manage resources?

An explicit move constructor or a move operator can be specified for any class, whether it has pointer members or not. It is true that move constructors and operators are usually used with classes that have pointers of some kind. But that's only because in these cases there's a way to effect a move that avoids the more expensive overhead of copying.

A default move operation for a trivial type is equivalent to a copy operation.

A default move operation for a class is equivalent to a move operation for each member of the class.

Therefore, in your example, the move operation is equivalent to a copy operation, and has no observable differences.

However it is certainly possible to need a move operator for a class that has no pointer members, for whatever reason. A classical example is std::thread, which has a move constructor and an assignment operator but not the equivalent copy constructor and copy assignment operator. The contents of std::thread are implementation defined, but typically consist of an opaque handle for an operating system-specific thread identifier. This handle cannot be "copied" in any meaningful way, but its ownership can be meaningful transferred to another std::thread, and that's why you have move operators there.

Your own classes may have similar semantical requirements, too, even if they don't have any pointers of any kind.

Why object could be moved even lacks move constructor and move assignment operator?

So I think gcc is going to complain to me that below class due to lack of move constructor and move assignment operator.

Because the operations required could be performed via copy constructor and copy assignment operator. Interface still has copy constructor and copy assignment operator which are implcitly declared. And rvalues are always possible to be bound to const Interface&.

More precisely, even without move constructor and move assignment operator provided, Interface is still MoveConstructible,

A class does not have to implement a move constructor to satisfy this
type requirement: a copy constructor that takes a const T& argument
can bind rvalue expressions.

and MoveAssignable.

The type does not have to implement move assignment operator in order
to satisfy this type requirement: a copy assignment operator that
takes its parameter by value or as a const Type&, will bind to rvalue
argument.


BTW: If you make the move constructor and move assignment operator delete explicitly then both copy and move operation would fail. Using with an rvalue expression the explicitly deleted overload will be selected and then fails. Using with an lvalue expression would fail too because copy constructor and copy assignment operator are implicitly declared as deleted because of the declaration of move constructor or move assignment operator.

C++ default move assignment operator cannot be invoked on class even if class has no constructors defined

If you look carefully, std::mutex cannot be copied nor moved. Since you have a member that cannot be moved or copied, your move constructor and copy constructor are implicitly deleted. If you want to allow moving your class, you can always use std::unique_ptr.

struct ThreadSafeFile {
std::unique_ptr<std::mutex> m_mutex;
string m_FileName;
std::ofstream m_File;
};

As T.C pointed out in the comments, implementing a move constructor that moves everything except the mutex can also be a valid solution for some cases. You can find a very good example here: How should I deal with mutexes in movable types in C++?



Related Topics



Leave a reply



Submit