Assignment from rvalue allowed when assignment operator explicitly deleted? - c++

Consider the following code, which compiles under Clang, GCC, and VS 2015 (online example):
#include <utility>
class S
{
public:
S(int x) : i(x) { }
~S() { }
S(S&&) = default;
S(const S& ) = delete;
S& operator=(S&&) = delete;
S& operator=(const S&) = delete;
private:
int i;
};
S GetS()
{
// This is a contrived example with details elided. Assume
// there's a reason in the actual use case for returning via
// std::move.
return std::move( S(999) );
}
int main()
{
S tmp = GetS(); // <-- Assignment allowed even though assignment operator is deleted?
return 1;
}
I'm unclear as to why the line
S tmp = GetS();
compiles, executing the move constructor instead of the move assignment operator.
I know that RVO allows construction and assignment to be elided as an optimization, but it was my understanding that explicitly deleting an operator should cause compilation to fail if that operator is explicitly used in the code.
Is there some clause in the C++11 specification that allows a compiler to transform assignment initialization into copy construction, even when assignment operators for a type have been explicitly deleted?

That's because this isn't assignment:
S tmp = GetS();
it's called copy-initialization and invokes the move constructor, which you have explicitly defaulted.
Basically, an assignment operator is only invoked on an object that already exists. tmp doesn't exist yet, this statement initializes it. As such, you're calling a constructor.
Note that what happens inside of GetS() does not affect the rules for how tmp is constructed. GetS() is an rvalue either way.

Related

Confusing behaviour around mandatory elision of copy/move [duplicate]

The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:
#include <cassert>
struct S {
int i;
int *p;
S() : i(0), p(&i) {}
// S(const S &s) : i(s.i), p(&i) {} // #1
// S(const S &s) : i(s.i), p(s.p) {} // #2
// S(const S &s) = delete; // #3
};
S make_S() {return S{};}
int main()
{
S s = make_S();
assert(s.p == &s.i);
}
With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.
However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?
Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
In your case, when I made both copy and move constructors defaulted:
S(const S &) = default;
S(S &&) = default;
assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.

Does the behavior of guaranteed copy elision depend on existence of user-defined copy constructor?

The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:
#include <cassert>
struct S {
int i;
int *p;
S() : i(0), p(&i) {}
// S(const S &s) : i(s.i), p(&i) {} // #1
// S(const S &s) : i(s.i), p(s.p) {} // #2
// S(const S &s) = delete; // #3
};
S make_S() {return S{};}
int main()
{
S s = make_S();
assert(s.p == &s.i);
}
With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.
However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?
Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
In your case, when I made both copy and move constructors defaulted:
S(const S &) = default;
S(S &&) = default;
assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.

Move constructor is automatic generated even a member doesn't have a move constructor?

Quoted from C++ Primer
if we explicitly ask the compiler to generate a move operation by
using = default, and the compiler is unable to move all the
members, then the move operation will be defined as deleted
the move constructor is defined as deleted if the
class has a member that defines its own copy constructor but does not also
define a move constructor, or if the class has a member that doesn’t define its
own copy operations and for which the compiler is unable to synthesize a move
constructor
Some code seems to violate this rule:
#include <utility>
#include <iostream>
struct X {
X() = default;
X(const X&) { std::cout << "X(const X&)" << std::endl; }
int i;
};
struct hasX {
hasX() = default;
hasX(const hasX &) = delete;
hasX(hasX &&) = default;
X mem;
};
int main()
{
hasX hx, hx2 = std::move(hx); //output is X(const X&)
}
X doesn't define move constructor and compiler can't synthesize one for it.
According to the rule above, hasX's move constructor is deleted.
However, because hasX's copy constructor is deleted, hx2 = std::move(hx) must call move constructor to output "X(const X&)", which shows hasX's move constructor is defined and it use X's copy constructor to "move". This seems to violate the rule above.
So, is't defined in C++ standard or just a compiler implementation?
Compiler I tested: VS2015 and a online compiler
Thank you for help!
Your book seems to be wrong. Copying is a valid move operation so as long as the member has a copy constructor in the form of const type&, so it can bind to rvalues, the move operation will fall back to a copy.
In your example
hasX(hasX &&) = default;
Can be replaced with
hasX(hasX &&rhs) : mem(std::move(rhs.mem)) {}
as that is what the default does and it will compile just fine.

When Does Move Constructor get called?

I'm confused about when a move constructor gets called vs a copy constructor.
I've read the following sources:
Move constructor is not getting called in C++0x
Move semantics and rvalue references in C++11
msdn
All of these sources are either overcomplicated(I just want a simple example) or only show how to write a move constructor, but not how to call it. Ive written a simple problem to be more specific:
const class noConstruct{}NoConstruct;
class a
{
private:
int *Array;
public:
a();
a(noConstruct);
a(const a&);
a& operator=(const a&);
a(a&&);
a& operator=(a&&);
~a();
};
a::a()
{
Array=new int[5]{1,2,3,4,5};
}
a::a(noConstruct Parameter)
{
Array=nullptr;
}
a::a(const a& Old): Array(Old.Array)
{
}
a& a::operator=(const a&Old)
{
delete[] Array;
Array=new int[5];
for (int i=0;i!=5;i++)
{
Array[i]=Old.Array[i];
}
return *this;
}
a::a(a&&Old)
{
Array=Old.Array;
Old.Array=nullptr;
}
a& a::operator=(a&&Old)
{
Array=Old.Array;
Old.Array=nullptr;
return *this;
}
a::~a()
{
delete[] Array;
}
int main()
{
a A(NoConstruct),B(NoConstruct),C;
A=C;
B=C;
}
currently A,B,and C all have different pointer values. I would like A to have a new pointer, B to have C's old pointer, and C to have a null pointer.
somewhat off topic, but If one could suggest a documentation where i could learn about these new features in detail i would be grateful and would probably not need to ask many more questions.
A move constructor is called:
when an object initializer is std::move(something)
when an object initializer is std::forward<T>(something) and T is not an lvalue reference type (useful in template programming for "perfect forwarding")
when an object initializer is a temporary and the compiler doesn't eliminate the copy/move entirely
when returning a function-local class object by value and the compiler doesn't eliminate the copy/move entirely
when throwing a function-local class object and the compiler doesn't eliminate the copy/move entirely
This is not a complete list. Note that an "object initializer" can be a function argument, if the parameter has a class type (not reference).
a RetByValue() {
a obj;
return obj; // Might call move ctor, or no ctor.
}
void TakeByValue(a);
int main() {
a a1;
a a2 = a1; // copy ctor
a a3 = std::move(a1); // move ctor
TakeByValue(std::move(a2)); // Might call move ctor, or no ctor.
a a4 = RetByValue(); // Might call move ctor, or no ctor.
a1 = RetByValue(); // Calls move assignment, a::operator=(a&&)
}
First of all, your copy constructor is broken. Both the copied from and copied to objects will point to the same Array and will both try to delete[] it when they go out of scope, resulting in undefined behavior. To fix it, make a copy of the array.
a::a(const a& Old): Array(new int[5])
{
for( size_t i = 0; i < 5; ++i ) {
Array[i] = Old.Array[i];
}
}
Now, move assignment is not being performed as you want it to be, because both assignment statements are assigning from lvalues, instead of using rvalues. For moves to be performed, you must be moving from an rvalue, or it must be a context where an lvalue can be considered to be an rvalue (such as the return statement of a function).
To get the desired effect use std::move to create an rvalue reference.
A=C; // A will now contain a copy of C
B=std::move(C); // Calls the move assignment operator
Remember that copy elision could occur. If you disable it by passing the -fno-elide-constructors flag to the compiler your constructor might get executed.
You can read about it here: https://www.geeksforgeeks.org/copy-elision-in-c/
Answers above do not give a 'natural' example when a move constructor is called. I found this way to call move constructor without std::move (and without suppressing copy elision by -fno-elide-constructors):
a foo(a a0) {
return a0; // move ctor is called
}
a a1 = foo(a());

What are all the member-functions created by compiler for a class? Does that happen all the time?

What are all the member-functions created by compiler for a class? Does that happen all the time? like destructor.
My concern is whether it is created for all the classes, and why is default constructor needed?
C++98/03
If they are needed,
the compiler will generate a default constructor for you unless you declare any constructor of your own.
the compiler will generate a copy constructor for you unless you declare your own.
the compiler will generate a copy assignment operator for you unless you declare your own.
the compiler will generate a destructor for you unless you declare your own.
As Péter said in a helpful comment, all those are only generated by the compiler when they are needed. (The difference is that, when the compiler cannot create them, that's Ok as long as they aren't used.)
C++11
C++11 adds the following rules, which are also true for C++14 (credits to towi, see this comment):
The compiler generates the move constructor if
there is no user-declared copy constructor, and
there is no user-declared copy assignment operator, and
there is no user-declared move assignment operator and
there is no user-declared destructor,
it is not marked deleted,
and all members and bases are moveable.
Similarly for move assignment operator, it is generated if
there is no user-declared copy constructor, and
there is no user-declared copy assignment operator, and
there is no user-declared move constructor and
there is no user-declared destructor,
it is not marked deleted,
and all members and bases are moveable.
Note that these rules are a bit more elaborate than the C++03 rules and make more sense in practice.
For an easier understanding of what is what in the above:
class Thing {
public:
Thing(); // default constructor
Thing(const Thing&); // copy c'tor
Thing& operator=(const Thing&); // copy-assign
~Thing(); // d'tor
// C++11:
Thing(Thing&&); // move c'tor
Thing& operator=(Thing&&); // move-assign
};
Further reading: if you are a C++-beginner consider a design that does not require you to implement any of five a.k.a The Rule Of Zero originally from an article written by Martinho Fernandes.
Do you mean 'defined' by 'created'?
$12.1 - "The default constructor (12.1), copy constructor and copy assignment operator (12.8), and destructor (12.4) are special member functions.
If 'created' means 'defined' then, here are the important parts from the C++ Standard.
-An implicitly-declared default constructor for a class is implicitly defined when it is used to create an object of its class type (1.8).
-If a class has no user-declared destructor, a destructor is declared implicitly. An implicitly-declared destructor is implicitly defined when it is used to destroy an object of its class type.
-If the class definition does not explicitly declare a copy constructor, one is declared implicitly. An implicitly-declared copy constructor is implicitly defined if it is used to initialize an object of its class type from a copy of an object of its class type or of a class type derived from its class type).
-If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly. An implicitly-declared copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type.
C++17 N4659 standard draft
https://github.com/cplusplus/draft/blob/master/papers/n4659.pdf 6.1 "Declarations and definitions" has a note which likely summarizes all of them:
3
[ Note: In some circumstances, C ++ implementations implicitly define the default constructor (15.1), copy
constructor (15.8), move constructor (15.8), copy assignment operator (15.8), move assignment operator (15.8),
or destructor (15.4) member functions. — end note ] [ Example: Given
#include <string>
struct C {
std::string s; // std::string is the standard library class (Clause 24)
};
int main() {
C a;
C b = a;
b = a;
}
the implementation will implicitly define functions to make the definition of C equivalent to
struct C {
std::string s;
C() : s() { }
C(const C& x): s(x.s) { }
C(C&& x): s(static_cast<std::string&&>(x.s)) { }
// : s(std::move(x.s)) { }
C& operator=(const C& x) { s = x.s; return *this; }
C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; }
// { s = std::move(x.s); return *this; }
~ C() { }
};
— end example ]
The conditions under which those are declared are explained at: Conditions for automatic generation of default/copy/move ctor and copy/move assignment operator?
A cool way to ensure that something has a default is to try and make it use = default as explained at: What does "default" mean after a class' function declaration?
The example below does that, and also exercises all the implicitly defined functions.
#include <cassert>
#include <string>
struct Default {
int i;
Default() = default;
Default(const Default&) = default;
Default& operator=(Default&) = default;
Default& operator=(const Default&) = default;
Default(Default&&) = default;
Default& operator=(Default&&) = default;
~Default() = default;
};
struct Instrument {
int i;
static std::string last_call;
Instrument() { last_call = "ctor"; }
Instrument(const Instrument&) { last_call = "copy ctor"; }
Instrument& operator=(Instrument&) { last_call = "copy assign"; return *this; }
Instrument& operator=(const Instrument&) { last_call = "copy assign const"; return *this; }
Instrument(Instrument&&) { last_call = "move ctor"; }
Instrument& operator=(Instrument&&) { last_call = "move assign"; return *this; }
~Instrument() { last_call = "dtor"; }
};
std::string Instrument::last_call;
int main() {
// See what the default constructors are doing.
{
// Default constructor.
Default ctor;
// i is uninitialized.
// std::cout << ctor.i << std::endl;
ctor.i = 1;
// Copy constructor.
Default copy_ctor(ctor);
assert(copy_ctor.i = 1);
// Copy assignment.
Default copy_assign;
copy_assign = ctor;
assert(copy_assign.i = 1);
// Copy assignment const.
const Default const_ctor(ctor);
Default copy_assign_const;
copy_assign_const = const_ctor;
assert(copy_assign_const.i == 1);
// Move constructor.
Default move_ctor(std::move(ctor));
assert(move_ctor.i == 1);
// Move assignment.
Default move_assign;
move_assign = std::move(ctor);
assert(move_assign.i == 1);
}
// Check that the constructors are called by these calls.
{
// Default constructor.
Instrument ctor;
assert(Instrument::last_call == "ctor");
// Copy constructor.
Instrument copy_ctor(ctor);
assert(Instrument::last_call == "copy ctor");
// Copy assignment.
copy_ctor = ctor;
assert(Instrument::last_call == "copy assign");
// Copy assignment const.
const Instrument const_ctor(ctor);
Instrument copy_assign_const;
copy_assign_const = const_ctor;
assert(Instrument::last_call == "copy assign const");
// Move constructor.
Instrument move_ctor(std::move(ctor));
assert(Instrument::last_call == "move ctor");
// Move assignment.
Instrument move_assign;
move_assign = std::move(ctor);
assert(Instrument::last_call == "move assign");
// Destructor.
{
Instrument dtor;
}
assert(Instrument::last_call == "dtor");
}
}
GitHub upstream.
Tested with GCC 7.3.0:
g++ -std=c++11 implicitly_defined.cpp
By default, if not implemented by the user, the compiler add some member functions to the class. Those are called the big four :
default constructor
copy constructor
copy operator (assignment)
destructor
Depending on the types of the members and which member function listed you provide yourself, those will not all be generated.
Other answers have told you what's created, and that the compiler may only generate them if used.
My concern is whether it is created for all the classes...
Why concerned? Thinking it's creating unwanted code in the executable? Unlikely, but you can check easily enough with your environment.
Or perhaps your concern was that it might not create a constructor when you want one? Nothing to worry about... they're always created if needed and not provided by the user.
...and why is default constructor needed?
Because classes may have objects inside them with their own destructors that need to be systematically invoked. For example, given...
struct X
{
std::string a;
std::string b;
};
...the default destructor makes sure the destructors for a and b run.