Hello, and welcome to C++ Weekly.
I'm your host, Jason Turner. I am available for contracting, code reviews and on-site training.
In this episode I am going to mention function-try-blocks
and this is one of the weirdest little corners of C++
that I do not believe I have ever seen anyone actually use in production code.
But it does have one potentially useful place where it can be utilized.
So we're all familiar with how a function is defined in C++, I hope.
So we've got our function; it does nothing.
Now it is entirely possible to say that we want to create a function that is a try block.
This function can catch anything that might happen, and this is a valid function.
So as you can see, we can call this as a function; it does nothing ...
... and there's no like typical body of the function here like you would expect to see.
And just to drive home the point of what we're looking at,
... you can do this.
The indentation is weird. We don't even know how to deal with this at all. No one does this.
Is this how we should format it? Should it be on its own line? Should it be indented?
I have no idea. No one uses this feature. But it does have one useful place where it can be applied.
Now I'm sure someone could leave a comment or something as to where it's useful in a regular function,
but by far I think the most useful scenario is with constructors.
So we've got some the value that's being passed into our constructor here. And we've got, well, we're just gonna have to create another type.
We've got something that likes to throw. And we're going to go ahead and move that out here for legibility.
This is one of the least meaningful classes that i've ever created.
If you try to construct it with a value for an integer that is greater than two it's going to throw that integer right back in your face.
So we've got our ThrowingThing here.
We're going to create one inside of our struct S.
And we want to initialize it with our value.
Now this should look familiar to you. Hopefully you are using member initializers in your structs in C++.
This is a really clean way to manage the lifetime of your things.
But we have created a minor problem, and that is:
It is entirely possible for our ThrowingThing to throw a thing back at us.
We want to create our struct S and we will be very proud of it and we're going to pass it the value 5 to its constructor.
And now we've got something that is guaranteed to throw an exception on construction.
But let's say we wanted to handle this more intelligently.
We might be able to use our function-try-blocks
And getting the syntax for this just right is a little goofy.
So, we actually need to put the 'try' before the member initializers ...
... and then the catch down here, so we can actually catch this integer at construction time.
Now, we are probably left wondering what exactly has happened here,
... because THIS could never be constructed into a valid state, and we have caught our integer.
So is S constructed or not at this point? And that is an absolutely great question.
So we can look and try to see what the compiler did to see if we can tell.
So, we can see that the compiler, even though optimizations are fully enabled,
... in our main is actually making a call to our ThrowingThing(int) constructor
... and then it's doing all kinds of stack management.
xor eax (function returns 0)
So the ThrowingThing() constructor compares against 2 and then jumps if greater than 2 ...
... to label seven (.L7), which we know it is going to be greater than 2. Our exception is allocated and thrown.
Well, we can see this final call to throw, right here,
is pretty much going to be the end of it, because it does not seem that
anything is able to catch that exception.
So, we might be able to take this one step further, and see if we can actually get something out of this.
Well, the compiler is certainly generating code for this return of value 5, but I don't believe that it is ever actually called.
And this is where it gets interesting to compare to another compiler,
because we can see that set a value of 5 to eax and the return value of main is never actually generated in clang. So, yeah.
You can catch the exception, you can log it, and you could do something interesting here,
... but you don't really have any way of ever leaving your struct in a valid state.
But this does give you a way to catch, log, and in some way handle an exception that is thrown ...
... during the construction of a sub-object in a structure by using a function try block.
And as you can see here on cppReference.com, in the definition of what must happen, it says:
"Every catch clause in the function-try- block for a constructor must terminate by throwing an exception.
"If the control reaches the end of such handler the current exception is automatically rethrown as if by throw."
So there is no way to escape from your function-try-block of a constructor without throwing an exception.
But it does give you some chance to deal with this in an intermediate way.
And we also see that: "Reaching the end of a catch clause for a function-try-block on a destructor ...
" ... also automatically rethrows the current exception as if by throw; but a return statement is allowed."
So that's interesting because ...
Would you also have a use to do this in your destructor?
Destructors that throw are not a good thing.
But a regular function, that's not a constructor or a destructor has more flexibility.
Be sure to subscribe, follow me on twitter, and check out any of the links below.