Scope of Exception Object in C++

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_casts 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.

  1. 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.

  2. 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).

  3. 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.

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 catchs 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



Leave a reply



Submit