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, andthe 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 aconst 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 aconst 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
What Is the Windows Equivalent For En_Us.Utf-8 Locale
Libpng Warning: Iccp: Known Incorrect Srgb Profile
Catching Access Violation Exceptions
How to Get Rid of 'Deprecated Conversion from String Constant to 'Char*'' Warnings in Gcc
Global Memory Management in C++ in Stack or Heap
What Are the Gcc Default Include Directories
How to Call a Base Class'S Virtual Function If I'M Overriding It
Determine the Line of Code That Causes a Segmentation Fault
When Should I Use Std::Thread::Detach
How to Convert a Lambda to an Std::Function Using Templates
How Does Generic Lambda Work in C++14
How to Perform a Bitwise Operation on Floating Point Numbers
What Constitutes a Valid State For a "Moved From" Object in C++11