Item 17: Understand special member function generation.
Move operations are generated only for classed lacking
explicitly declared move operation, copy operations,
or a destructor.
Now, when I refer to a move operation move-constructing
or move-assigning a data member or base class, there
is no guarantee that a move will actually take place.
"Memberwise moves" are, in reality, more like memberwise
move requests, because types that aren't move-enabled(...)
will be "moved" via their copy operations.
However, I can not verify them on my environment.
// compiled
#include <iostream>
using namespace std;
class Base {
public:
~Base() {}
};
int main() {
Base a, c;
Base b(move(a));
c = move(b);
// explicitly destructor does not disable
// default move constuctor and move assignment operator
return 0;
}
class Base {
public:
Base() {}
Base(Base& b) {}
~Base() {}
};
class Num {
private:
Base b;
};
int main() {
Num a, c;
c = move(a); // passed
Num b(move(c)); // error
// explicitly Base::Base(Base& b) disable default move
// move conctructor.
// Num's default move constructor can not find any move
// constructor for member object Base b, which lead to an
// error. Num's default move constructor does not "moved"
// Base b via their copy operations which is declared.
return 0;
}
The first assertion might be vary from different environments, but the second one is almost wrong.
I am very confusing about it.
Please help me out.
class Base {
public:
~Base() {}
};
Because Base has a user-declared destructor, it does not have a move constructor or move assignment operator at all. The lines
Base b(move(a));
c = move(b);
actually call the copy constructor and copy assignment operator, respectively.
class Base {
public:
Base() {}
Base(Base& b) {}
~Base() {}
};
class Num {
private:
Base b;
};
Again, Base has no move constructor at all. However, Num does have an implicitly-declared move constructor, since Num itself does not declare any special member functions. However, it is implicitly defined as deleted, since the default definition would be ill-formed:
Num::Num(Num&& n) : b(std::move(n.b)) {}
// Cannot convert rvalue of type `Base` to `Base&`
// for the copy constructor to be called.
Note that the "move" of b does attempt to use the copy constructor, but it can't.
Related
Simplified code snippet is:
class A {
public:
~A();
static A create();
private:
A() = default;
A(A&&) = default;
NonCopyable n;
};
A A::create() {
A a;
return a;
}
int main(int argc, char* argv[]) {
auto a = A::create();
return 0;
}
Please also see my live example (which shows different compilers' behavior).
In the end, I'm wondering why does auto a = A::create(); compile without errors using newer compilers [gcc >= 7.1] (which part of the C++17 standard is relevant here?), given that:
We have a non-copyable member NonCopyable n;, so default copy constructor would be ill-formed.
It's an NRVO here since A a; return a; so copy elision is not guaranteed by the standard.
Move constructor A(A&&) is marked private.
Optimizations were off -O0 for testing.
My suspicion is that move constructor is being "validated" by the compiler at return a;; since this is a member function of A it passes the validation. Even if the suspicion is correct, I'm not sure if this is standard-compliant.
I believe this is a consequence of P0135: Wording for guaranteed copy elision through simplified value categories, specifically the change to [dcl.init]:
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object.
[Example: T x = T(T(T())); calls the T default constructor to initialize x. — end example]
As a result, this behavior is not dependent on copy elision of return values or the availability of move constructors.
class A { public: int x[100]; };
Declaring A a will not initialize the object (to be seen by garbage values in the field x).
The following will trigger initialization: A a{} or auto a = A() or auto a = A{}.
Should any particular one of the three be preferred?
Next, let us make it a member of another class:
class B { public: A a; };
The default constructor of B appears to take care of initialization of a.
However, if using a custom constructor, I have to take care of it.
The following two options work:
class B { public: A a; B() : a() { } };
or:
class B { public: A a{}; B() { } };
Should any particular one of the two be preferred?
Initialization
class A { public: int x[100]; };
Declaring A a will not initialize the object (to be seen by garbage
values in the field x).
Correct A a is defined without an initializer and does not fulfill any of the requirements for default initialization.
1) The following will trigger initialization:
A a{};
Yes;
a{} performs list initialization which
becomes value initialization if {} is empty, or could be aggregate initialization if A is an aggregate.
Works even if the default constructor is deleted. e.g. A() = delete; (If 'A' is still considered an aggregate)
Will warn of narrowing conversion.
2) The following will trigger initialization:
auto a = A();
Yes;
This is copy initialization where a prvalue temporary is constructed with direct initialization () which
uses value initialization if the () is empty.
No hope of aggregate initialization.
The prvalue temporary is then used to direct-initialize the object.
Copy elision may be, and normally is employed, to optimize out the copy and construct A in place.
Side effects of skipping copy/move constructors are allowed.
Move constructor may not be deleted. e.g A(A&&) = delete;
If copy constructor is deleted then move constructor must be present. e.g. A(const A&) = delete; A(A&&) = default;
Will not warn of narrowing conversion.
3) The following will trigger initialization:
auto a = A{}
Yes;
This is copy initialization where a prvalue temporary is constructed with list initialization {} which
uses value initialization if {} is empty, or could be aggregate initialization if A is an aggregate.
The prvalue temporary is then used to direct-initialize the object.
Copy elision may be, and normally is employed, to optimize out the copy and construct A in place.
Side effects of skipping copy/move constructors are allowed.
Move constructor may not be deleted. e.g A(A&&) = delete;
If copy constructor is deleted then move constructor must be present. e.g. A(const A&) = delete; A(A&&) = default;
Will warn of narrowing conversion.
Works even if the default constructor is deleted. e.g. A() = delete; (If 'A' is still considered an aggregate)
Should any particular one of the three be preferred?
Clearly you should prefer A a{}.
Member Initialization
Next, let us make it a member of another class:
class B { public: A a; };
The default constructor of B appears to take care of initialization
of a.
No this is not correct.
the implicitly-defined default constructor of 'B' will call the default constructor of A, but will not initialize the members. No direct or list initialization will be triggered. Statement B b; for this example will call the default constructor, but leaves indeterminate values of A's array.
1) However, if using a custom constructor, I have to take care of it. The
following two options work:
class B { public: A a; B() : a() { } };
This will work;
: a() is a constructor initializer and a() is a member initializer as part of the member initializer list.
Uses direct initialization () or, if () is empty, value initialization.
No hope of using aggregate initialization.
Will not warn of narrowing conversion.
2) or:
class B { public: A a{}; B() { } };
This will work;
a now has a non-static data member initializer, which may require a constructor to initialize it if you are using aggregate initialization and the compiler is not fully C++14 compliant.
The member initializer uses list initialization {} which
may become either value initialization if {} is empty or aggregate initialization if A is an aggregate.
If a is the only member then the default constructor does not have to be defined and the default constructor will be implicitly defined.
Clearly you should prefer the second option.
Personally, I prefer using braces everywhere, with some exceptions for auto and cases where a constructor could mistake it for std::initializer_list:
class B { public: A a{}; };
A std::vector constructor will behave differently for std::vector<int> v1(5,10) and std::vector<int> v1{5,10}. with (5,10) you get 5 elements with the value 10 in each one, but with {5,10} you get two elements containing 5 and 10 respectively because std::initializer_list is strongly preferred if you use braces. This is explained very nicely in item 7 of Effective Modern C++ by Scott Meyers.
Specifically for member initializer lists, two formats may be considered:
Direct initialization a() which becomes value initialization if the () is empty.
List initialization a{} which also becomes value initialization if {} is empty.
In member initializer lists, fortunately, there is no risk of the most vexing parse. Outside of the initializer list, as a statement on its own, A a() would have declared a function vs. A a{} which would have been clear. Also, list initialization has the benefit of preventing narrowing conversions.
So, in summary the answer to this question is that it depends on what you want to be sure of and that will determine the form you select. For empty initializers the rules are more forgiving.
What is copy elision? What is (named) return value optimization? What do they imply?
In what situations can they occur? What are limitations?
If you were referenced to this question, you're probably looking for the introduction.
For a technical overview, see the standard reference.
See common cases here.
Introduction
For a technical overview - skip to this answer.
For common cases where copy elision occurs - skip to this answer.
Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).
It's the only form of optimization that elides (ha!) the as-if rule - copy elision can be applied even if copying/moving the object has side-effects.
The following example taken from Wikipedia:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
Depending on the compiler & settings, the following outputs are all valid:
Hello World!
A copy was made.
A copy was made.
Hello World!
A copy was made.
Hello World!
This also means fewer objects can be created, so you also can't rely on a specific number of destructors being called. You shouldn't have critical logic inside copy/move-constructors or destructors, as you can't rely on them being called.
If a call to a copy or move constructor is elided, that constructor must still exist and must be accessible. This ensures that copy elision does not allow copying objects which are not normally copyable, e.g. because they have a private or deleted copy/move constructor.
C++17: As of C++17, Copy Elision is guaranteed when an object is returned directly:
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C(); //Definitely performs copy elision
}
C g() {
C c;
return c; //Maybe performs copy elision
}
int main() {
std::cout << "Hello World!\n";
C obj = f(); //Copy constructor isn't called
}
Common forms of copy elision
For a technical overview - skip to this answer.
For a less technical view & introduction - skip to this answer.
(Named) Return value optimization is a common form of copy elision. It refers to the situation where an object returned by value from a method has its copy elided. The example set forth in the standard illustrates named return value optimization, since the object is named.
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
Regular return value optimization occurs when a temporary is returned:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
return Thing();
}
Thing t2 = f();
Other common places where copy elision takes place is when an object is constructed from a temporary:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
void foo(Thing t);
Thing t2 = Thing();
Thing t3 = Thing(Thing()); // two rounds of elision
foo(Thing()); // parameter constructed from temporary
or when an exception is thrown and caught by value:
struct Thing{
Thing();
Thing(const Thing&);
};
void foo() {
Thing c;
throw c;
}
int main() {
try {
foo();
}
catch(Thing c) {
}
}
Common limitations of copy elision are:
multiple return points
conditional initialization
Most commercial-grade compilers support copy elision & (N)RVO (depending on optimization settings). C++17 makes many of the above classes of copy elision mandatory.
Standard reference
For a less technical view & introduction - skip to this answer.
For common cases where copy elision occurs - skip to this answer.
Copy elision is defined in the standard in:
12.8 Copying and moving class objects [class.copy]
as
31) When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class
object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases,
the implementation treats the source and target of the omitted copy/move operation as simply two different
ways of referring to the same object, and the destruction of that object occurs at the later of the times
when the two objects would have been destroyed without the optimization.123 This elision of copy/move
operations, called copy elision, is permitted in the following circumstances (which may be combined to
eliminate multiple copies):
— in a return statement in a function with a class return type, when the expression is the name of a
non-volatile automatic object (other than a function or catch-clause parameter) with the same cvunqualified
type as the function return type, the copy/move operation can be omitted by constructing
the automatic object directly into the function’s return value
— in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a
function or catch-clause parameter) whose scope does not extend beyond the end of the innermost
enclosing try-block (if there is one), the copy/move operation from the operand to the exception
object (15.1) can be omitted by constructing the automatic object directly into the exception object
— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved
to a class object with the same cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the omitted copy/move
— when the exception-declaration of an exception handler (Clause 15) declares an object of the same type
(except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted
by treating the exception-declaration as an alias for the exception object if the meaning of the program
will be unchanged except for the execution of constructors and destructors for the object declared by
the exception-declaration.
123) Because only one object is destroyed instead of two, and one copy/move constructor is not executed, there is still one
object destroyed for each one constructed.
The example given is:
class Thing {
public:
Thing();
~Thing();
Thing(const Thing&);
};
Thing f() {
Thing t;
return t;
}
Thing t2 = f();
and explained:
Here the criteria for elision can be combined to eliminate two calls to the copy constructor of class Thing:
the copying of the local automatic object t into the temporary object for the return value of function f()
and the copying of that temporary object into object t2. Effectively, the construction of the local object t
can be viewed as directly initializing the global object t2, and that object’s destruction will occur at program
exit. Adding a move constructor to Thing has the same effect, but it is the move construction from the
temporary object to t2 that is elided.
Copy elision is a compiler optimization technique that eliminates unnecessary copying/moving of objects.
In the following circumstances, a compiler is allowed to omit copy/move operations and hence not to call the associated constructor:
NRVO (Named Return Value Optimization): If a function returns a class type by value and the return statement's expression is the name of a non-volatile object with automatic storage duration (which isn't a function parameter), then the copy/move that would be performed by a non-optimising compiler can be omitted. If so, the returned value is constructed directly in the storage to which the function's return value would otherwise be moved or copied.
RVO (Return Value Optimization): If the function returns a nameless temporary object that would be moved or copied into the destination by a naive compiler, the copy or move can be omitted as per 1.
#include <iostream>
using namespace std;
class ABC
{
public:
const char *a;
ABC()
{ cout<<"Constructor"<<endl; }
ABC(const char *ptr)
{ cout<<"Constructor"<<endl; }
ABC(ABC &obj)
{ cout<<"copy constructor"<<endl;}
ABC(ABC&& obj)
{ cout<<"Move constructor"<<endl; }
~ABC()
{ cout<<"Destructor"<<endl; }
};
ABC fun123()
{ ABC obj; return obj; }
ABC xyz123()
{ return ABC(); }
int main()
{
ABC abc;
ABC obj1(fun123()); //NRVO
ABC obj2(xyz123()); //RVO, not NRVO
ABC xyz = "Stack Overflow";//RVO
return 0;
}
**Output without -fno-elide-constructors**
root#ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Constructor
Constructor
Destructor
Destructor
Destructor
Destructor
**Output with -fno-elide-constructors**
root#ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root#ajay-PC:/home/ajay/c++# ./a.out
Constructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Move constructor
Destructor
Constructor
Move constructor
Destructor
Destructor
Destructor
Destructor
Destructor
Even when copy elision takes place and the copy-/move-constructor is not called, it must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed.
You should permit such copy elision only in places where it won’t affect the observable behavior of your software. Copy elision is the only form of optimization permitted to have (i.e. elide) observable side-effects. Example:
#include <iostream>
int n = 0;
class ABC
{ public:
ABC(int) {}
ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect
}; // it modifies an object with static storage duration
int main()
{
ABC c1(21); // direct-initialization, calls C::C(42)
ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )
std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
return 0;
}
Output without -fno-elide-constructors
root#ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp
root#ajay-PC:/home/ayadav# ./a.out
0
Output with -fno-elide-constructors
root#ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors
root#ajay-PC:/home/ayadav# ./a.out
1
GCC provides the -fno-elide-constructors option to disable copy elision.
If you want to avoid possible copy elision, use -fno-elide-constructors.
Now almost all compilers provide copy elision when optimisation is enabled (and if no other option is set to disable it).
Conclusion
With each copy elision, one construction and one matching destruction of the copy are omitted, thus saving CPU time, and one object is not created, thus saving space on the stack frame.
Here I give another example of copy elision that I apparently encountered today.
# include <iostream>
class Obj {
public:
int var1;
Obj(){
std::cout<<"In Obj()"<<"\n";
var1 =2;
};
Obj(const Obj & org){
std::cout<<"In Obj(const Obj & org)"<<"\n";
var1=org.var1+1;
};
};
int main(){
{
/*const*/ Obj Obj_instance1; //const doesn't change anything
Obj Obj_instance2;
std::cout<<"assignment:"<<"\n";
Obj_instance2=Obj(Obj(Obj(Obj(Obj_instance1)))) ;
// in fact expected: 6, but got 3, because of 'copy elision'
std::cout<<"Obj_instance2.var1:"<<Obj_instance2.var1<<"\n";
}
}
With the result:
In Obj()
In Obj()
assignment:
In Obj(const Obj & org)
Obj_instance2.var1:3
I'm looking into move semantics from C++11 and I'm curious how to move fundamental types like boolean, integer float etc. in the constructor. Also the compound types like std::string.
Take the following class for example:
class Test
{
public:
// Default.
Test()
: m_Name("default"), m_Tested(true), m_Times(1), m_Grade('B')
{
// Starting up...
}
Test(const Test& other)
: m_Name(other.m_Name), m_Times(other.m_Times)
, m_Grade(other.m_Grade), m_Tested(other.m_Tested)
{
// Duplicating...
}
Test(Test&& other)
: m_Name(std::move(other.m_Name)) // Is this correct?
{
// Moving...
m_Tested = other.m_Tested; // I want to move not copy.
m_Times = other.m_Times; // I want to move not copy.
m_Grade = other.m_Grade; // I want to move not copy.
}
~Test()
{
// Shutting down....
}
private:
std::string m_Name;
bool m_Tested;
int m_Times;
char m_Grade;
};
How do I move (not copy) m_Tested, m_Times, m_Grade. And is m_Name moved correctly? Thank you for your time.
Initialization and assignment of a primitive from a prvalue or xvalue primitive has exactly the same effect as initialization or assignment from a lvalue primitive; the value is copied and the source object is unaffected.
In other words, you can use std::move but it won't make any difference.
If you want to change the value of the source object (to 0, say) you'll have to do that yourself.
Looks correct. Except simple data types like bool, int, char are only copied. The point of "moving" a string is that it has a buffer that it normally has to copy when constructing a new object, however when moving the old buffer is used (copying the pointer and not the contents of the buffer).
Test(Test&& other)
: m_Name(std::move(other.m_Name)), m_Times(other.m_Times)
, m_Grade(other.m_Grade), m_Tested(other.m_Tested)
{}
I'm trying to create and initialize a class that contains a member array of a non-trivial class, which contains some state and (around some corners) std::atomic_flag. As of C++11 one should be able to initialize member arrays.
The code (stripped down to minimum) looks like this:
class spinlock
{
std::atomic_flag flag;
bool try_lock() { return !flag.test_and_set(std::memory_order_acquire); }
public:
spinlock() : flag(ATOMIC_FLAG_INIT){};
void lock() { while(!try_lock()) ; }
void unlock() { flag.clear(std::memory_order_release); }
};
class foo
{
spinlock lock;
unsigned int state;
public:
foo(unsigned int in) : state(in) {}
};
class bar
{
foo x[4] = {1,2,3,4}; // want each foo to have different state
public:
//...
};
If I understand the compiler output correctly, this seems not to construct the member array, but to construct temporaries and invoke the move/copy constructor, which subsequently calls move constructors in sub-classes, and that one happens to be deleted in std::atomic_flag. The compiler output that I get (gcc 4.8.1) is:
[...] error: use of deleted function 'foo::foo(foo&&)'
note: 'foo::foo(foo&&)' is implicitly deleted because the default definition would be ill-formed
error: use of deleted function 'spinlock::spinlock(spinlock&&)'
note: 'spinlock::spinlock(spinlock&&)' is implicitly deleted because [...]
error: use of deleted function 'std::atomic_flag::atomic_flag(const std::atomic_flag&)'
In file included from [...]/i686-w64-mingw32/4.8.1/include/c++/atomic:41:0
[etc]
If I remove the array and instead just put a single foo member inside bar, I can properly initialize it using standard constructor initializers, or using the new in-declaration initialization, no problem whatsoever. Doing the same thing with a member array fails with the above error, no matter what I try.
I don't really mind that array elements are apparently constructed as temporaries and then moved rather than directly constructed, but the fact that it doesn't compile is obviously somewhat of a showstopper.
Is there a way I either force the compiler to construct (not move) the array elements, or a way I can work around this?
Here's a minimal example exposing the problem:
struct noncopyable
{
noncopyable(int) {};
noncopyable(noncopyable const&) = delete;
};
int main()
{
noncopyable f0 = {1};
noncopyable f1 = 1;
}
Although the two initializations of f0 and f1 have the same form (are both copy-initialization), f0 uses list-initialization which directly calls a constructor, whereas the initialization of f1 is essentially equivalent to foo f1 = foo(1); (create a temporary and copy it to f1).
This slight difference also manifests in the array case:
noncopyable f0[] = {{1}, {2}, {3}, {4}};
noncopyable f1[] = {1, 2, 3, 4};
Aggregate-initialization is defined as copy-initialization of the members [dcl.init.aggr]/2
Each member is copy-initialized from the corresponding initializer-clause.
Therefore, f1 essentially says f1[0] = 1, f1[1] = 2, .. (this notation shall describe the initializations of the array elements), which has the same problem as above. OTOH, f0[0] = {1} (as an initialization) uses list-initialization again, which directly calls the constructor and does not (semantically) create a temporary.
You could make your converting constructors explicit ;) this could avoid some confusion.
Edit: Won't work, copy-initialization from a braced-init-list may not use an explicit constructor. That is, for
struct expl
{
explicit expl(int) {};
};
the initialization expl e = {1}; is ill-formed. For the same reason, expl e[] = {{1}}; is ill-formed. expl e {1}; is well-formed, still.
Gcc refuses to compile list-initialization of arrays of objects with virtual destructor up to version 10.2. In 10.3 that was fixed.
E.g. if noncopyable from #dyp answer had a virtual destructor, gcc fails to compile line:
noncopyable f0[] = {{1}, {2}, {3}, {4}};
arguing to deleted copy and move c-rs. But successfully compiles under 10.3 and higher.