Referring to http://en.wikipedia.org/wiki/Copy_elision
I run below code:
#include <iostream>
struct C {
C() {}
C(const C&) { std::cout << "Hello World!\n"; }
};
void f() {
C c;
throw c; // copying the named object c into the exception object.
} // It is unclear whether this copy may be elided.
int main() {
try {
f();
}
catch(C c) { // copying the exception object into the temporary in the exception declaration.
} // It is also unclear whether this copy may be elided.
}
The Output I got:
Gaurav#Gaurav-PC /cygdrive/d/Trial
$ make clean
rm -f Trial.exe Trial.o
Gaurav#Gaurav-PC /cygdrive/d/Trial
$ make
g++ -Wall Trial.cpp -o Trial
Gaurav#Gaurav-PC /cygdrive/d/Trial
$ ./Trial
Hello World!
Hello World!
I understand that the compiler might have optimized the code with unnecessary copying, which it is not doing here.
But What I want to ask, How does two calls to the copy constructor is being made?
catch(C c) - Since we are passing by value, hence here the copy constructor is being called.
But at throw c how is copy constructor being called? Can someone explain?
throw c;
Creates a temporary object and it is this temporary object that is thrown. The creation of the temporary might be through copy/move constructor. And yes this copy/move can be elided.
References:
C++11 15.1 Throwing an exception
§3:
A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type.........
§5:
When the thrown object is a class object, the copy/move constructor and the destructor shall be accessible, even if the copy/move operation is elided (12.8).
Copy & Move constructor while throwing user-defined type object
struct demo
{
demo() = default;
demo(demo &&) = delete;
demo(const demo &) = delete;
};
int main()
{
throw demo{};
return 0;
}
Upon throw expression, a copy of the exception object always needs to be created as the original object goes out of the scope during the stack unwinding process.During that initialization, we may expect copy elision (see this) – omits copy or move constructors (object constructed directly into the storage of the target object). But even though copy elision may or may not be applied you should provide proper copy constructor and/or move constructor which is what C++ standard mandates(see 15.1). See below compilation error for reference.
error: call to deleted constructor of 'demo'
throw demo{};
^~~~~~
note: 'demo' has been explicitly marked deleted here
demo(demo &&) = delete;
^
1 error generated.
compiler exit status 1
If we catch an exception by value, we may also expect copy elision(compilers are permitted to do so, but it is not mandatory). The exception object is an lvalue argument when initializing catch clause parameters.
From: 7 best practices for exception handling in C++
But at throw c how is copy constructor being called? Can someone explain?
C++ exceptions must be copy/move constructable if you want to do throw ex; as what's happening behind the scene is that the C++ ABI will allocate an exception object (via __cxa_allocate_exception) somewhere and copy/move your exception object, either it's on the heap or stack, before it actually starts the stack unwinding process.
Reference https://blog.the-pans.com/cpp-exception-2/
Related
I have the following:
#include <iostream>
#include <utility>
class T {
public:
T() {std::cout << "Default constructor called" << std::endl;}
T(const T&) {std::cout << "Copy constructor called" << std::endl;}
};
static T s_t;
T foo() {
return s_t;
}
class A {
public:
A() = delete;
A(T t) : _t(t) {}
private:
T _t;
};
int main() {
std::cout << "Starting main" << std::endl;
A a(foo());
return 0;
}
When I compile it with the line:
g++ -std=c++17 -O3 test.cpp and run it, I get the following output
Default constructor called
Starting main
Copy constructor called
Copy constructor called
My question is: since the constructor of A is taking an r-value object of type T that is only used to initialize _t, is it possible for the compiler to avoid the second copy operation and just copy s_t directly into _t?
(I understand that I can potentially replace the second copy with a move by implementing a move constructor for T)
Your copy constructor has an observable side effect (output). The (almost) golden rule of C++ optimizations is the as-if rule: Observable side effects must not be changed. But one of the two exceptions from this rule is the elision of some copy operations, specified here:
http://eel.is/c++draft/class.copy.elision
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects.
But this is only allowed in very specific circumstances:
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 [...]
Your variable does not have automatic storage duration (i.e. it is not a local variable).
in a throw-expression, [...]
We are not a throw-expression.
in a coroutine [...]
We are not in a coroutine.
when the exception-declaration of an exception handler ([except]) declares an object of the same type (except for cv-qualification) as the exception object ([except.throw]) [...]
This is not the case either.
Neither the return of a global variable from a function, nor the use of a constructor parameter to initialize a member variable is covered here, so it would be illegal for any compiler to elide these copies in the code you show.
That said, if the compiler can prove that there are no side effects (which, in the case of most compilers, include system calls and memory allocations), it is of course free to elide the copy under the as-if rule, just like for all other optimizations.
This question already has answers here:
What are copy elision and return value optimization?
(5 answers)
Closed 4 years ago.
Today I encountered something I don't really understand about copy constructor.
Consider the next code:
#include <iostream>
using namespace std;
class some_class {
public:
some_class() {
}
some_class(const some_class&) {
cout << "copy!" << endl;
}
some_class call() {
cout << "is called" << endl;
return *this; // <-- should call the copy constructor
}
};
some_class create() {
return some_class();
}
static some_class origin;
static some_class copy = origin; // <-- should call the copy constructor
int main(void)
{
return 0;
}
then the copy constructor is called when assigning origin to copy, which makes sense. However, if I change the declaration of copy into
static some_class copy = some_class();
it isn't called. Even when I am using the create() function, it does not call the copy constructor.
However, when changing it to
static some_class copy = some_class().call();
it does call the copy constructor.
Some research explained that the compiler is allowed to optimize the copy-constructor out, which sound like a good thing. Until the copy-constructor is non-default, as then it might, or might not do something obvious, right?
So when is it allowed for the compiler to optimize out the copy-constructor?
Since C++17, this kind of copy elision is guaranteed, the compiler is not just allowed, but required to omit the copy (or move) construction, even if the copy (or move) constructor has the observable side-effects.
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:
In the initialization of a variable, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T x = T(T(T())); // only one call to default constructor of T, to initialize x
In a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:
T f() {
return T();
}
f(); // only one call to default constructor of T
Your code snippet matches these two cases exactly.
Before C++17, the compiler is not required, but allowed to omit the copy (or move) construction, even if the copy/move constructor has observable side-effects. Note that it's an optimization, even copy elision takes place the copy (or move) constructor still must be present and accessible.
On the other hand, call() doesn't match any conditions of copy elision; it's returning *this, which is neither a prvalue nor a object with automatic storage duration, copy construction is required and can't be omitted.
I have 3 questions :
1. It is always said to catch the exception object by reference. In the following example i see that the destructor is called before the catch block executed, that means we are referring to an object in catch which must have gone out of scope by the time we use it. Why use reference then?
class MyException{
public:
~MyException() {
cout<<"Dtor for MyException called \n";
}
};
int main()
{
try{
MyException obj1;
throw obj1;
}
catch(MyException& obj)
{
cout<<"Catched unhandled exception";
}
}
2. Why is the destructor called twice here? Once before entering the catch block and second time after execution of catch completes.
3. The example in Lifetime of a thrown object caught by reference shows that the destructor is called just once i.e. after the catch block exits ONLY WHEN a copy constructor is defined in the class.
a. What is the role of copy constructor here when we are catching it by refrence?
b. Even after defining a copy constructor it never gets called anyways. So what impact does it make?
c. I tried to define a copy constructor in my example too as below but still i see destructor getting called twice.Why?:
MyException(const MyException& obj)
{
}
Can somebody answer all the 5 questions(3rd question has 3 parts).
You're throwing a copy of the exception you created as a local temporary. So there's two: the first is destroyed when you throw, the second (the copy) after the catch block is done.
Try this:
try { throw MyException{}; }
catch (MyException const& obj) { }
Edit: If your copy constructor is really not being called in your code then my guess is that the compiler has recognized that the exception class is empty? Compiler is free to perform any optimizations it chooses so long as the behavior of the code is as-if it hadn't. Exception to this is copy-ellision but if that was going on you'd not get the double destructor call.
throw ALWAYS makes a copy of an object you're throwing. You can't throw an object which is not copyable. If fact, throw can even fail if it can't allocate enough memory for a copy. This should explain why you see a destructor being called inside a try-block. It's a destructor of your local obj1. It goes out of scope as soon as your throw a copy of it.As for catching by reference, it is used to avoid unnecessary copying and, more importantly, potential slicing.
Once again, it was a destructor of your local obj1. It went after out scope when you threw a copy of it.
a) If you catch by reference, no copying is performed. Copying (with potential slicing) is performed ONLY if you catch by value.
b) You're wrong, throw calls a copy constructor to, well, make a copy of your object and throw it. The only case when a copy constructor won't be called is when you throw a temporary object and compiler elides a copy.
c) Refer to my first and second answer.
They recommend you take your catch block by reference to avoid slicing (since inheriting exceptions is standard) and to avoid needlessly copying, since throw copies unconditionally.
You cannot receive obj1 by reference as throw obj1 causes it to be destroyed (since it is no longer in scope). Due to this throw always copies to temporary memory location that you don't control.
obj1 and the temporary I mentioned before. obj is a reference to that temporary, the referenced temporary will be destroyed after the end of the catch block and before the next operation (similar to if it were actually local to the catch block).
A single destructor only happens when the copy is elided and the standard doesn't guarantee when that will happen, as copy elision is always optional. Defining a copy constructor makes no difference as the compiler defines it for you if you don't define it (and it can be created). Deleting the copy constructor would just result in throw being illegal.
a. As above, copy constructor to copy into the temporary. Reference to reference said temporary.
b. Did you actually delete the copy constructor? MyException(const MyException&) = delete; is how you delete a copy constructor, failing to define it just causes the compiler to make one for you. And to repeat, deleting it causes throw to be illegal.
c. Because the copy isn't elided and two objects exist, a temporary and your object.
I've come across some exceptions issue that is unclear to me. In C++, when an object is thrown it is first copied to a temporary object, and the temporary object is then passed to the catching code. The copy involves the use of the object's class copy constructor. AFAIK, this means that if a class has a private copy constructor, it can't be used as an exception. However, in VS2010, the following code compiles and runs:
class Except
{
Except(const Except& other) { i = 2; }
public:
int i;
Except() : i(1) {}
};
int main()
{
try
{
Except ex1;
throw ex1; // private copy constructor is invoked
}
catch (Except& ex2)
{
assert(ex2.i == 2); // assert doesn't yell - ex2.i is indeed 2
}
return 0;
}
Is this legal?
It's not legal. Standard 15.1/5
If the use of the temporary object can be eliminated without changing
the meaning of the program except for the execution of constructors
and destructors associated with the use of the temporary object
(12.2), then the exception in the handler can be initialized directly
with the argument of the throw expression. When the thrown object is a
class object, and the copy constructor used to initialize the
temporary copy is not accessible, the program is ill-formed (even when
the temporary object could otherwise be eliminated). Similarly, if the
destructor for that object is not accessible, the program is
ill-formed (even when the temporary object could otherwise be
eliminated).
No, it's not.
15.1.5 When the thrown object is a class object, the copy/move constructor and the destructor shall be accessible,
even if the copy/move operation is elided
As far as i know, a copy constructor is invoked in the following scenarios :
1) Pass by value
2) Return by value
3) When you create and initialize a new object with an existing object
Here's the program :
#include <iostream>
using namespace std;
class Example
{
public:
Example()
{
cout << "Default constructor called.\n";
}
Example(const Example &ob1)
{
cout << "Copy constructor called.\n";
}
Example& operator=(const Example &ob1)
{
cout << "Assignment operator called.\n";
return *this;
}
~Example()
{
cout<<"\nDtor invoked"<<endl;
}
int aa;
};
Example funct()
{
Example ob2;
ob2.aa=100;
return ob2;
}
int main()
{
Example x;
cout << "Calling funct..\n";
x = funct();
return 0;
}
The output is:
Default constructor called.
Calling funct..
Default constructor called.
Assignment operator called.
Dtor invoked
Dtor invoked
Please correct me, IIRC the following sequence of calls should occur :
1) Constructor of x is called
2) Constructor of ob2 is called
3) The function returns and so copy constructor is invoked (to copy ob2 to unnamed temporary variable i.e funct() )
4) Destructor of ob2 called
5) Assign the unnamed temporary variable to x
6) Destroy temporary variable i.e invoke its destructor
7) Destroy x i.e invoke x's destructor
But then why copy constructor is not invoked and also only 2 calls to dtors are there whereas i expect 3.
I know compiler can do optimizations, however, is my understanding correct ?
Thanks a lot :)
Regards
lali
A copy constructor might not be invoked when you return by value. Some compilers use return value optimization feature.
Read about "Return Value Optimization"
The part of the standard which tells you when compilers may elide copies is 12.8/15. It's always up to the compiler whether to do actually perform the elision. There are two legal situations, plus any combination of them:
"in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object with the same cv-unqualified type as the function return type"
"when a temporary class object that has not been bound to a reference would be copied to a class object with the same cv-unqualified type".
The former is usually referred to as the "named return value optimization", and it's what permits the output you're seeing in your example. The latter in effect turns copy-initialization into direct initialization, and could occur for instance if your code did Example x = Example();.
Other copy elisions are not permitted, except of course that the usual "as-if" rules apply. So if the copy constructor has tracing in, then the following code must call it:
Example x;
Example y = x;
But if x were otherwise unused, and the cctor had no side-effects, then I think it could be optimized away, just like any other code that does nothing.
When doing x = funct(); the compiler notices that it will be directly returned and thus avoids a useless construction. That's also why you will only get two destructor calls.
This is a example why sometimes working with "copy" isn't necessarily a lost of performances.
In your exampe the structure is small enough therefore it is passed through a register. The generated code is similar to Return value optimization. Construct a more complicated example, and you'll see the behavior expected.
g++ v4.4.1 has an option to suppress "elide" optimizations:
tst#u32-karmic$ g++ -fno-elide-constructors Example.cpp -o Example
tst#u32-karmic$ ./Example
Default constructor called.
Calling funct..
Default constructor called.
Copy constructor called.
Dtor invoked
Assignment operator called.
Dtor invoked
Dtor invoked
As you can see the copy constructor is now called!