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
Related
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.
This is a spinoff from invoking the copy constructor within the constructor.
I believe that an object is fully formed and can be expected to behave as such by the end of the initialiser list (edit: I was wrong about this though!). Specifically, member functions and accessing local state from within the constructor itself will behave exactly as they would from any other member function.
This seems to be a slightly contentious point of view though, the alternative is that only once the constructor has returned normally is the object fully formed.
The following is a quick & dirty test case for this which shows all the member fields that are mentioned in the initialiser list being initialised and those that aren't getting default constructed.
#include <cstdio>
struct noise
{
noise() { printf("noise default constructed\n"); }
noise(int x) { printf("noise integer constructed %u\n", x); }
~noise() { printf("noise dtor\n"); }
};
struct invoke : public noise
{
noise init;
noise body;
invoke() : noise(3), init(4)
{
body = noise(5);
throw *this; // try to use the object before returning normally
}
~invoke() { printf("invoke dtor\n"); }
};
int main()
{
try
{
invoke i;
}
catch (...)
{
}
}
This prints, on my machine at least,
noise integer constructed 3
noise integer constructed 4
noise default constructed
noise integer constructed 5
noise dtor
noise dtor
noise dtor
noise dtor
invoke dtor
noise dtor
noise dtor
noise dtor
As always, it's difficult to distinguish works-as-specified from works-as-my-compiler-implemented! Is this actually UB?
Is an object fully constructed at the end of the initialiser list?
No it is not. The object this is fully constructed at the end of the execution of the constructor.
However, all the members are constructed by the end of the initializer list.
The difference is subtle but it is important as it relates to the execution of the destructors. Every constructed member and base class is destructed if the this object throws an exception during the execution of the constructor. The destructor of the this object will only execute once it is fully constructed.
From the cppreference:
For any object of class or aggregate types if it, or any of its subobjects, is initialized by anything other than the trivial default constructor, lifetime begins when initialization ends.
For any object of class types whose destructor is not trivial, lifetime ends when the execution of the destructor begins.
Your example is well-defined behavior, but only just so.
To be clear, the "invoke dtor" line we're seeing is from the destruction of your exception, not the top-level i object.
Each member of the class is initialized by the time the constructor body starts, though the object itself is not initialized until the constructor body completes. This is why ~invoke is not called on the top-level invoke object.
The throw *this expression copy-initializes an invoke object from *this, which is allowed. (The standard explicitly states that "Member functions [...] can be called during construction or destruction".) Your copy-initialization is the default, which just copy-initializes all the members - which have all been initialized.
Then because your constructor body is exiting via exception, all the initialized members are destructed, the exception is propagated, caught, and then disposed of, calling ~invoke, and in turn destroying those members as well.
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.
why copy constructor is called when we return an object from a method by value. please see my below code in that i am returning an object from a method while returning control is hitting the copy constructor and then returning. i am not understood following things:
1) why it is calling copy constructor.
2)which object is passing implicitly to copy constructor,
3)to which object copy constructor will copy the content,
4)what is the necessity of copying the object content while returning. so plz help.
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
class ClassA
{
int a, b;
public:
ClassA()
{
a = 10;
b = 20;
}
ClassA(ClassA &obj)
{
cout << "copy constructor called" << endl;
}
};
ClassA function (ClassA &str)
{
return str;
}
int main ()
{
ClassA str;
function(str);
//function(str);
return 0;
}
I'll try to answer all the questions at once.
The behavior you are observing is due to the way returning an object by value works in C++. First, a temporary object is copy-constructed (or move-constructed, in C++11) from the value returned by your function. Then, if this return value is used to initialize another object, such as in:
Class c = fxn();
The object c is copy-constructed (or move-constructed, in C++11), from that temporary.
This said, an implementation is allowed to elide one (in your concrete case) or both of these calls to the copy or move constructor per paragraph 12/8.31 of the C++11 Standard even though those constructors have side effects, and construct the function's return value directly in c:
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. 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.122
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 cv-unqualified
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
— [...]
— 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
— [...]
The reason why I wrote that in your specific case only one of the calls to the copy or move constructor can be elided is the sentence in bold in the first bullet of the Standard quote above:
[...] (other than a function or catch-clause parameter) [...]
If you are returning a parameter of your function, copy elision is inhibited.
Also notice, that the signature of your constructor should be:
Class(Class const& c)
// ^^^^^
There is no reason for you to accept an lvalue reference to non-const in the copy constructor, since you are not going to modify the object you are copying from.
Even worse, the above would prevent copy construction from rvalues (like temporaries), so the following code would not compile:
Class foo() { return Class(); }
Even though an implementation is allowed to elide the copy, a viable and accessible copy constructor (or move constructor, if we're talking about a move) should still be present.
The function returns an object. Hence, that object must exist. Hence, that object must be created from somewhere. Clearly this means that one of its constructors shall be used. The question is, which one?
Since you chose to return str;, this is the instruction that shall be used to create it. How else could you use that return instruction to create and return an object and yet not use the copy constructor? It is clear that you need to use str to initialize the returned value, so you're not going to use the other option (the parameterless constructor).
The copy constructor is called because you call by value not by reference. Therefore a new object must be instantiated from your current object since all members of the object should have the same value in the returned instance. Because otherwise you would be returning the object it self, which would be returning by reference. In this case modifying the reference object would change the original as well. This is generally not a behavior one wants when returning by value.
ClassA function (ClassA &str)
{
return str;
}
object str will copy-constructored in a temporary object with type ClassA for further usage. However compiler can omit it due to optimization.
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/