Scope of exception object in C++
When a throw
expression is evaluated, an exception object is initialized from the value of the expression. The exception object which is thrown gets its type from the static type of the throw expression ignoring any const
and volatile
qualifiers. For class types this means that copy-initialization is performed.
The exception object's scope is outside of the scope of the block where the throw occurs. Think of it as living in a special exception area off to one side of the normal call stack where local objects live.
Inside a catch
block, the name initialized with the caught exception object is initialized with this exception object and not the argument to throw
, even if this was an lvalue.
If you catch
via non-const reference, then you can mutate the exception object, but not what it was initialized from. You can alter the behaviour of the program if you re-throw the exception in ways that you couldn't if you caught by value or const reference (const_cast
s aside).
The exception object is destroyed when the last catch block that does not exit via a re-throw (i.e. a parameterless throw expression evaluation) completes.
about : Scope of exception object in C++ : why don't I get the copy?
I would expect 2 copies to happen.
Why? Only one copy is made by the catch
block. Where would the second copy happen?
set err_code to 1.1, but the display is still 1.
Because get_code
returns an int
, so the floating point value gets truncated.
Scope of Exception object when caught(using catch) by reference
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 asthrow obj1
causes it to be destroyed (since it is no longer in scope). Due to thisthrow
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 causesthrow
to be illegal.c. Because the copy isn't elided and two objects exist, a temporary and your object.
C++: If an exception is thrown, are objects that go out of scope destroyed?
Yes.
C++ Standard n3337
15 Exception handling
§ 15.2 Constructors and destructors
1) As control passes from a throw-expression to a handler, destructors
are invoked for all automatic objects constructed since the try block
was entered. The automatic objects are destroyed in the reverse order
of the completion of their construction.2) An object of any storage duration whose initialization or
destruction is terminated by an exception will have destructors
executed for all of its fully constructed subobjects (excluding the
variant members of a union-like class), that is, for subobjects for
which the principal constructor (12.6.2) has completed execution and
the destructor has not yet begun execution. Similarly, if the
non-delegating constructor for an object has completed execution and a
delegating constructor for that object exits with an exception, the
object’s destructor will be invoked. If the object was allocated in a
new-expression, the matching deallocation function (3.7.4.2, 5.3.4,
12.5), if any, is called to free the storage occupied by the object.3) The process of calling destructors for automatic objects
constructed on the path from a try block to a throw-expression is
called “stack unwinding.” If a destructor called during stack
unwinding exits with an exception, std::terminate is called (15.5.1).
[ Note: So destructors should generally catch exceptions and not let
them propagate out of the destructor. — end note ]
example:
SomeClass c; // declared before try{} so it is
// still valid in catch{} block
try {
SomeClass t;
throw;
} catch( ...) {
// t destroyed
// c valid
}
C++: In throwing/catching exceptions, when are the exception objects destructed?
If you're going to mark the constructors of an object, mark all of them ;)
struct BASE_EX {
static int count;
int id;
BASE_EX() : id(count++) {
std::cout << "constructing BASE_EX " << id << std::endl; // usually std::endl is unnecessary (it's just "\n" followed by std::flush), but since we're playing with crashes it's probably a good idea
}
BASE_EX(BASE_EX const &other) : id(count++) {
std::cout << "copying BASE_EX " << other.id << " as BASE_EX " << id << std::endl;
}
// implicit move constructor not declared
virtual std::string what() const { return "BASE_EX " + std::to_string(id); } // marking by-value return as const does absolutely nothing
~BASE_EX() { std::cout << "destructing BASE_EX " << id << std::endl; } // reminder that base class destructors should generally be virtual; not required in this case
};
int BASE_EX::count = 0;
struct DERIVED_EX : BASE_EX {
static int count;
int id;
DERIVED_EX() : BASE_EX(), id(count++) {
std::cout << "constructing DERIVED_EX " << id << std::endl;
}
DERIVED_EX(DERIVED_EX const &other) : BASE_EX(other), id(count++) {
std::cout << "copying DERIVED_EX " << other.id << " as DERIVED_EX " << id << std::endl;
}
// implicit move constructor not declared
std::string what() const override { return "DERIVED_EX " + std::to_string(id); }
~DERIVED_EX() { std::cout << "destructing DERIVED_EX " << id << std::endl; }
};
int DERIVED_EX::count = 0;
You get
constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
Second catch block: BASE_EX 1
destructing BASE_EX 1
The first throw
sets the exception object to be DERIVED_EX 0
. The inner catch
gets a reference to the BASE_EX 0
base class subobject of that exception object. Since what
is virtual
, calling it causes the DERIVED_EX
to report its type. However, when you throw ex
again, ex
only has static type BASE_EX
, so the new exception object is chosen to be a BASE_EX
, and it is created by copying only the BASE_EX
part of the first exception object. That first exception object is destroyed as we exit the first catch
, and the outer catch receives the new BASE_EX
object. Since it really is a BASE_EX
, not a DERIVED_EX
, calling what
reflects that. If you make both catch
s by-value, you get
constructing BASE_EX 0
constructing DERIVED_EX 0
copying BASE_EX 0 as BASE_EX 1
First catch block: BASE_EX 1
copying BASE_EX 1 as BASE_EX 2
destructing BASE_EX 1
destructing DERIVED_EX 0
destructing BASE_EX 0
copying BASE_EX 2 as BASE_EX 3
Second catch block: BASE_EX 3
destructing BASE_EX 3
destructing BASE_EX 2
When you catch
by-value, the exception object is copied to initialize the catch parameter. During the execution of such a catch
block, there are two objects representing the exception: the actual exception object, which has no name, and the copy of it made for the catch
block, which may be named. The first copy is the copy of the first exception object to the first catch
's parameter. The second copy is the copy of that parameter as the second exception object. The third is the copy of that exception object into the second catch
's parameter. The DERIVED_EX
part of the exception has been sliced off by the time we enter the first catch
. The catch
parameters are destroyed upon the end of each catch
, by the usual rules of scoping. The exception objects are destroyed whenever the corresponding catch
block exits.
You avoid the copying issues and the slicing issues by not taking exceptions by value and not using throw <catch-parameter>
to rethrow exceptions.
int main() {
try {
try {
throw DERIVED_EX();
} catch(BASE_EX const &ex) {
std::cout << "First catch block: " << ex.what() << std::endl;
throw;
}
} catch(BASE_EX const &ex) {
std::cout << "Second catch block: " << ex.what() << std::endl;
}
}
gives
constructing BASE_EX 0
constructing DERIVED_EX 0
First catch block: DERIVED_EX 0
Second catch block: DERIVED_EX 0
destructing DERIVED_EX 0
destructing BASE_EX 0
The exception object is not destroyed at the end of the first catch
because it exits with throw
, which signals for the same exception object to be used to match more catch
clauses. It's not copied into a new exception object and destroyed like throw ex
would call for.
Please see cppreference for a detailed description of the rules.
Object declaration in try and catch not defined in scope
Simple: if your code using object
depends on its creation not raising an exception (it does, since the object is not available if the exception occurs), then it's covered by the same condition as the object's creation. Simply move all code using object
into the try
block:
try {
Object object(value);
object.usingExemple();
} catch (exception) {
return 1;
}
As to why the error itself happens: The block statement in try
introduces a nested scope just like any other pair of braces. All declarations in that scope are local to the scope - so the identifier object
no longer exists outside that scope.
Be careful that my code above is not entirely equivalent to your original code, in that exceptions thrown by usingExample()
will now also be caught (thanks to @immibis for pointing that out). If that is not acceptable for you, you'll have to resort to dynamic allocation:
std::unique_ptr<Object> objectPtr;
try {
objectPtr.reset(new Object(value));
} catch (exception)
return 1;
}
Object &object = *objectPtr;
object.usingExample();
In theory, you could also use placement new
, but I wouldn't recommend that as simply "too weird a solution for the problem at hand:"
alignas(Object) unsigned char objectStorage[sizeof(Object)];
Object *objectPtr;
try {
objectPtr = new (&objectStorage) Object(value);
} catch (exception) {
return 1;
}
try {
Object &object = *objectPtr;
object.usingExample();
} catch (...) {
objectPtr->~Object();
throw;
}
How it is right way to throw object as a reference in C++?
When you throw an exception, you actually generate a copy of the exception object. It is the copy that is received by the catch
block, so it is not out of scope even though the original object is.
If an object is created locally and thrown as an exception in C++, how can a local object be valid outside it's scope .i.e., in catch block?
A copy of the object is thrown.
C++: will an std::runtime_error object leak in a longjmp?
This is kind of complicated. About longjmp
's validity, the standard says:
A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any objects with automatic storage duration.
runtime_error
has a non-trivial destructor, so the question is whether the exception object has "automatic storage duration". It does not. This suggests that longjmp
should be fine.
In addition, exception object destruction can happen in one of two places:
The points of potential destruction for the exception object are:
when an active handler for the exception exits by any means other than rethrowing, immediately after the destruction of the object (if any) declared in the exception-declaration in the handler;
when an object of type std::exception_ptr that refers to the exception object is destroyed, before the destructor of std::exception_ptr returns.
longjmp
is not "rethrowing". So in theory, this should be fine thanks to bullet point 1.
That being said, never rely on this. I highly doubt that implementations of longjmp
handle this correctly, and even if some do, it's probably not something you can expect.
Is the C++ exception out of scope if caught by the outter level caller?
A copy of the exception is thrown, not the original object. There's no need to instantiate a local variable and throw it; the idiomatic way to throw an exception is to instantiate it and throw it on the same line.
throw ConnectionLostEx("Connection lost low signals", 102);
Related Topics
What Is the Easiest Way to Print a Variadic Parameter Pack Using Std::Ostream
Check Traits for All Variadic Template Arguments
Stack Overflow Caused by Recursive Function
How to Compile a Visual Studio Project from the Command-Line
What Are Top-Level Const Qualifiers
C++11: How to Alias a Function
Qt Signals (Queuedconnection and Directconnection)
😃 (And Other Unicode Characters) in Identifiers Not Allowed by G++
How to Compose Output Streams, So Output Goes Multiple Places at Once
Why Does Calling Std::String.C_Str() on a Function That Returns a String Not Work
How to Clone as Derived Object in C++
How to Map the Indexes of a Matrix to a 1-Dimensional Array (C++)
Double Free or Corruption After Queue::Push
Are Child Processes Created with Fork() Automatically Killed When the Parent Is Killed