My reference is to the code segment presented as part of the follow-up discussion on the below write-up:
Lambdas vs. Closures
The pertinent code segment is
struct PrintD
{
~PrintD()
{
cout << "dtor" << endl;
}
};
int main()
{
PrintD d;
auto f = [d](){
d;
};
cout << "--" << endl;
return 0;
}
When the above code is compiled using GCC without the -fno-elide-constructors option, the following output is generated:
--
dtor
dtor
When recompiled using the -fno-elide-constructors option, the following output is generated:
dtor
--
dtor
dtor
Is the understanding correct that in the latter case, the additional dtor print out corresponds to the temporary being destroyed, which is created during the capture of d by copy inside f?
The "extra" copy comes from the copy-initialization of f from the lambda (the lambda is a prvalue).
Since C++17's change to prvalue materialization, there is never any extra copy here. You should find that if compiling with -std=c++17 you never get the extra copy, even passing the elision flag.
A simpler code demonstrating the same issue would be auto f = PrintD();.
In C++14 you could mandate avoiding the copy by:
auto&& f = [d] () {d;};
although personally I'd trust the compiler to do it and assume we'll be moving onto C++17 soon enough.
Related
I am new to boost:asio. I need to pass shared_ptr as argument to handler function.
E.g.
boost::asio::post(std::bind(&::function_x, std::move(some_shared_ptr)));
Is using std::move(some_shared_ptr) correct? or should I use as below,
boost::asio::post(std::bind(&::function_x, some_shared_ptr));
If both are correct, which one is advisable?
Thanks in advance
Regards
Shankar
Bind stores arguments by value.
So both are correct and probably equivalent. Moving the argument into the bind is potentially more efficient if some_argument is not gonna be used after the bind.
Warning: Advanced Use Cases
(just skip this if you want)
Not what you asked: what if function_x took rvalue-reference arguments?
Glad you asked. You can't. However, you can still receive by lvalue reference and just move from that. because:
std::move doesn't move
The rvalue-reference is only there to indicate potentially-moved-from arguments enabling some smart compiler optimizations and diagnostics.
So, as long as you know your bound function is only executed once (!!) then it's safe to move from lvalue parameters.
In the case of shared-pointers there's actually a little bit more leeway, because moving from the shared-ptr doesn't actually move the pointed-to element at all.
So, a little exercise demonstrating it all:
Live On Coliru
#include <boost/asio.hpp>
#include <memory>
#include <iostream>
static void foo(std::shared_ptr<int>& move_me) {
if (!move_me) {
std::cout << "already moved!\n";
} else {
std::cout << "argument: " << *std::move(move_me) << "\n";
move_me.reset();
}
}
int main() {
std::shared_ptr<int> arg = std::make_shared<int>(42);
std::weak_ptr<int> observer = std::weak_ptr(arg);
assert(observer.use_count() == 1);
auto f = std::bind(foo, std::move(arg));
assert(!arg); // moved
assert(observer.use_count() == 1); // so still 1 usage
{
boost::asio::io_context ctx;
post(ctx, f);
ctx.run();
}
assert(observer.use_count() == 1); // so still 1 usage
f(); // still has the shared arg
// but now the last copy was moved from, so it's gone
assert(observer.use_count() == 0); //
f(); // already moved!
}
Prints
argument: 42
argument: 42
already moved!
Why Bother?
Why would you care about the above? Well, since in Asio you have a lot of handlers that are guaranteed to execute precisely ONCE, you can sometimes avoid the overhead of shared pointers (the synchronization, the allocation of the control block, the type erasure of the deleter).
That is, you can use move-only handlers using std::unique_ptr<>:
Live On Coliru
#include <boost/asio.hpp>
#include <memory>
#include <iostream>
static void foo(std::unique_ptr<int>& move_me) {
if (!move_me) {
std::cout << "already moved!\n";
} else {
std::cout << "argument: " << *std::move(move_me) << "\n";
move_me.reset();
}
}
int main() {
auto arg = std::make_unique<int>(42);
auto f = std::bind(foo, std::move(arg)); // this handler is now move-only
assert(!arg); // moved
{
boost::asio::io_context ctx;
post(
ctx,
std::move(f)); // move-only, so move the entire bind (including arg)
ctx.run();
}
f(); // already executed
}
Prints
argument: 42
already moved!
This is going to help a lot in code that uses a lot of composed operations: you can now bind the state of the operation into the handler with zero overhead, even if it's bigger and dynamically allocated.
C++11 introduced the possibility of delegating construction from one constructor to another - "delegating constructors".
But - when doing that, can we use the this pointer? e.g. as an argument to one of the constructors we're delegating to? It's not trivial to assume that we can while the "real constructor" has not actually been invoked yet.
For a definitive answer one would need to read the standard (and even there it might not be entirely clear, see #BenjaminBanner's comment). But in practice - yes, apparently we can use this in constructor delegation.
The following example:
#include <iostream>
struct A {
A(int x_, void* p_) : x(x_), p(p_) { }
A(void* p_) : A(0, p_) { }
A() : A(this) { }
int x;
void* p;
};
int main() {
A a;
std::cout << "&a == " << &a << "\n";
std::cout << "a.p == " << a.p << "\n";
}
compiles (GodBolt) with all of GCC, clang and MSVC. They do not warn about doing this, even with -Wall -Wpedantic -Wextra or /W4 for MSVC.
It also runs (coliru.com) and produces the expected output (at the link you have the g++-compiled version, clang++ can be checked there as well).
I am curious about the C++11 list-initialization. I defined a class:
class base{
base() { cout << "default ctor" << endl;}
base(std::initiazer_list<base> il) { cout << "list initialization << endl;}
base(const base &rhs) { cout << "copy ctor" << endl;}
}
In main function, I initialize two objects to test my class.
int main()
{
base obj{}; // default ctor
base obj2{obj}; // copy ctor
}
I learn from 《effective modern C++》 that calls using list-initialization syntax strongly prefer the overloads taking std::initializer_list.
So, in my case, I think the second ctor will be called, however, the third is called.
Can you explain why?
Demo
default ctor
copy ctor
list initialization
So, it's correct in the sense that first the copy constructor is called to copy obj to std::initializer_list<base> then the appropriate constructor overload (which is the one for initializer list) is called.
base obj2{obj}; // copy ctor
As you can see this consists of two calls, not one. One to copy, one to list initialize. List initializer is the strongly preferred overload. The copy is to initialize the argument to that overload.
Edit: gcc and clang vary on this irrespective of version.
GCC Head
Clang Head
I think what's happening is that libstdc++ implementation of std::initializer_list creates temporary object, whereas clang picks up the copy constructor instead of initializer list overload.
Edit2:
I have checked this on my local system, clang with -stdlib=libstdc++ does create temporary for std::initializer_list hence a call to copy constructor exists.
All compilers are correct that
In C++11/14 we have: Return value optimization, move semantics, some classes like unique_ptr which don't have copy ctor
Q1: What is a correct behavior of the code snippet below when DECLARE_COPY_CTOR is equal to 1 or to zero ?
Q2: Console application built with MSVC 2013 for code snippet below in DEBUG build for Win32 gives in console: A(), A(A&&), ~A(), ~A(). So looks like lvalue referencce "a" was used to bind to "A&&". Is it legal? I thought that only temporary objects can be a candidate for move.
Q3: In RELEASE build compiler choose to use Rvo (so output was: A(), ~A())
Is compiler free to choose is "a" in function scope is a canditate for move?
#include <stdio.h>
#include <iostream>
#include <memory>
#define DECLARE_COPY_CTOR 0
class A
{
public:
A() {puts("A()");}
~A() { puts("~A()"); }
#if DECLARE_COPY_CTOR
A(A&) { puts("A(A&)"); }
#endif
A(A&&) { puts("A(A&&)"); }
A& operator = (A&) { puts("A& operator = (A&)"); return *this; }
};
A F()
{
A a; // here a is lvalue
return a; // here a is still lvalue
}
int main()
{
auto i = F();
return 0;
}
Q1. What is a correct behavior of the code snippet below when DECLARE_COPY_CTOR is equal to 1 or to zero ?
DECLARE_COPY_CTOR has no effect on the behaviour. Copy constructor is not invoked in the program.
Q2. So looks like lvalue referencce "a"
a is an lvalue, but it isn't an lvalue reference.
Q2. Is it legal?
Yes, it is legal to return a non-copyable local lvalue. It will be moved.
Q3. Is compiler free to choose is "a" in function scope is a canditate for move?
If the type of returned local variable is movable, then it must be moved rather than copied. NRVO applies to this case, so the compiler is free to elide the move - just like it was free to elide the copy prior to c++11.
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