When is a function try block useful?

12,672

Solution 1

You use it in constructors to catch errors from initializers. Usually, you don't catch those errors, so this is a quite exceptional use.

Otherwise, it is useless: unless I'm proven wrong,

void f() try { ... } catch (...) { ... }

is strictly equivalent to

void f() { try { ... } catch (...) { ... } }

Solution 2

Function try block are useful for me in two contexts.

a) To have a catch all clause around main() allowing to write small utilities without having to worry about local error handling:

int main()
try {
    // ...
    return 0;
}
catch (...) {
    // handle errors
    return -1;
}

which is clearly just syntactic sugar for having a try/catch inside main() itself.

b) to handle exceptions thrown by base class constructors:

struct B {
     B() { /*might throw*/ }
};

struct A : B {
     A() 
     try : B() { 
         // ... 
     } 
     catch (...) {
         // handle exceptions thrown from inside A() or by B() 
     } 
};

Solution 3

Aside from the functional uses mentioned, you can use the function-try-block to save yourself one level of indentation. (Ack, an answer about coding styles!)

Typically you see examples with the function-try-block like so:

void f(/*...*/)
try {
   /*...*/
}
catch(/*...*/) {
    /*...*/
}

Where the function scope is indented to the same level as if there were no function-try-block. This can be useful when:

  • you have an 80 character column limit and would have to wrap lines given the extra indentation.
  • you are trying to retrofit some existing function with try catch and don't want to touch all the lines of the function. (Yeah, we could just use git blame -w.)

Though, for functions that are entirely wrapped with a function-try-block, I would suggest not alternating between some functions using function-try-blocks and some not within the same code base. Consistency is probably more important then line wrapping issues. :)

Solution 4

Notes regarding how function try blocks operate:

  • For constructors, a function try block encompasses the construction of data members and base-classes.

  • For destructors, a function try block encompasses the destruction of data members and base-classes. It gets complicated, but for C++11, you have to include noexcept(false) in the declaration of your destructor (or that of a base/member class) or any destruction exception will result in termination at the conclusion of the catch block. It may be possible to prevent this by putting a return statement in the catch block (but this won't work for constructors).

  • A catch block in a constructor or destructor must throw some exception (or it will implicitly rethrow the caught exception). It is not legal to simply return (at least in constructor's function catch block). Note, however, that you could call exit() or similar, which might make sense in some situations.

  • A catch block can't return a value, so it doesn't work for functions returning non-void (unless they intentionally terminate the program with exit() or similar). At least that is what I've read.

  • The catch block for a constructor-function-try can't reference data/base members since they will have either have 1) not been constructed or 2) been destructed prior to the catch. As such, function try blocks are not useful for cleaning up an object's internal state -- the object should already be completely "dead" by the time you get there. This fact makes it very dangerous to use function try blocks in constructors, since it is difficult to police this rule over time if your compiler(s) don't happen to flag it.

valid (legal) uses

  • Translating an exception (to a different type/message) thrown during the constructor or it's base/member constructors.
  • Translating or absorbing and exception thrown during the destructor or it's base/member destructors (destructor etiquette notwithstanding).
  • Terminating a program (perhaps with a useful message).
  • Some kind of exception logging scheme.
  • Syntactic sugar for void-returning functions that happen to need a fully encapsulating try/catch block.

Solution 5

It might be useful if you want to catch exceptions from constructor's initializer.

However, if you do catch exception in constructor that way, you have to either rethrow it or throw new exception (i.e. you cannot just normally return from constructor). If you do not rethrow, it just happens implicitly.

#include <iostream>

class A
{
public:
  A()
  try {
    throw 5;
  }
  catch (int) {
    std::cout << "exception thrown\n";
    //return; <- invalid
  }
};

int main()
{
  try {
    A a;
  }
  catch (...) {
    std::cout << "was rethrown";
  }
}
Share:
12,672

Related videos on Youtube

Nawaz
Author by

Nawaz

Following Rust and Haskell isocpp.org/wiki/faq Contact me on LinkedIn. Religion of C Correcting Grammar for Microsoft Products and Technology

Updated on March 18, 2020

Comments

  • Nawaz
    Nawaz about 4 years

    I'm wondering when programmers use function try blocks. When is it useful?

    void f(int i)
    try
    {
       if ( i  < 0 ) 
          throw "less than zero";
       std::cout << "greater than zero" << std::endl;
    }
    catch(const char* e)
    {
        std::cout << e << std::endl;
    }
    
    int main() {
            f(1);
            f(-1);
            return 0;
    }
    

    Output: (at ideone)

    greater than zero
    less than zero
    

    EDIT: As some people might think that the syntax of function defintion is incorrect (because the syntax doesn't look familiar), I've to say that no its not incorrect. Its called function-try-block. See §8.4/1 [dcl.fct.def] in the C++ Standard.

    • Johannes Schaub - litb
      Johannes Schaub - litb about 13 years
      I've seen it abused to implement void f() synchronized { } in C++.
    • Johannes Schaub - litb
      Johannes Schaub - litb about 13 years
      @Nawaz See drdobbs.com/184401728, "Finally, Java's synchronized keyword is applicable to entire methods..."
    • Alexandre C.
      Alexandre C. about 13 years
      @Johannes: put this as an answer so that we can upvote it.
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    ctor-initializer. Initialization lists are something else.
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    (a) Sugar is usually sweet. I dispute that that is "sweet". (b) Base class construction failure should usually result in derived class construction failure, and if your uncaught exception does not signify an exceptional case preventing base class construction, then you're abusing exceptions; this is a code smell IMO.
  • hkaiser
    hkaiser about 13 years
    Tomalek, I believe you're just to quick with your assessment. It's not always possible to handle the exceptions locally in the base class or member constructor, as you might not have control over that source code. And sometimes you can and have to continue executing even if the base class throws (for instance, a database connect or a file open, which may fail), but still finish constructing the derived class, leaving it in a valid state.
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    @hkaiser: If your base object throws but you can still have a derived object in "a valid state", I believe that you are using inheritance inappropriately. If the base object is not in a valid state, then the derived object can not be in a valid state. (Note, objects not classes.)
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    ctor-initializer. Initializer lists are something else.
  • Mike DeSimone
    Mike DeSimone about 13 years
    Sometimes you don't care what a constructor throws; you just want any exception caught and then a known exception thrown, because upstream there's a handler that needs to distinguish "my constructor threw for some reason" from "some other code of mine threw", preventing it from using the catch-all.
  • Potatoswatter
    Potatoswatter about 13 years
    @Tomalak: The catch block of the constructor always throws or rethrows. There is an implicit throw; added at the end of it. So, even as @hkaiser has written this, it does not imply the object is left valid.
  • Alexandre C.
    Alexandre C. about 13 years
    @Tomalak: I agree with you: the only valid case I would accept such a construct is when you inherit from a 3rd party class which throws an exception which is not in your tree, and that you want to convert. Every other case (and the same goes for member objects construction errors) smells.
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    @AlexandreC: That's my opinion, precisely, yes.
  • Mike DeSimone
    Mike DeSimone about 13 years
    @Tomalak: Sometimes I have no control over the inheritance pattern of thrown exceptions. For example, I may have a class that contains a member pulled in from some other library; I cannot force that library's exceptions (which may not be well documented) to be in my exception class hierarchy. I still want to catch their exceptions, along with system exceptions like bad_alloc and any new exceptions added later without having to rewrite my code. Thus the desire to use a catch-all in my constructor and throw a known exception under my control.
  • Lightness Races in Orbit
    Lightness Races in Orbit about 13 years
    @Mike: i.e. what Alexandre just said.
  • akavel
    akavel over 12 years
    WARNING: in constructors and destructors, the exception is still rethrown _implicitly_ after catch ends, so they are still nearly useless - see e.g.: informit.com/guides/content.aspx?g=cplusplus&seqNum=496
  • Alexandre C.
    Alexandre C. over 12 years
    @akavel: they are not useless. You may want to perform some cleanup or logging before having the exception rethrown. Or you may want to throw another exception (which is allowed). Actually, the behavior is understandable: if a subobject or a base failed to get constructed, then the object cannot be constructed successfully and has to throw.
  • Justin Time - Reinstate Monica
    Justin Time - Reinstate Monica about 8 years
    It would seem that while return statements aren't allowed in the catch-clause(s) of a constructor's function-try-block, they are allowed in a normal function's; in fact, reaching the end of a catch-clause on a normal function is equivalent to return; for functions that return void, and undefined otherwise. This is shown in the final example at en.cppreference.com/w/cpp/language/function-try-block , and code that includes a return statement in the catch-clause compiles on both GCC and Visual Studio.
  • Justin Time - Reinstate Monica
    Justin Time - Reinstate Monica about 8 years
    It would appear that destructors use a combination of ctor and normal function rules: Reaching the end of a catch-clause normally is equivalent to throw;, but return statements are allowed.
  • Justin Time - Reinstate Monica
    Justin Time - Reinstate Monica about 8 years
    For a program that demonstrates this behaviour on normal functions, see ideone.com/YM62JS .
  • Lightness Races in Orbit
    Lightness Races in Orbit about 6 years
    FWIW I used a function-try block last week to "rewrite" an exception thrown by the initialisation of a data member. I didn't even feel that bad about it... :)
  • Brian Bi
    Brian Bi over 4 years
    @akavel You're allowed to return from the catch-block of a destructor, in which case the exception is not rethrown, as the goal of properly destroying the object is considered accomplished. In the case of a constructor, I agree with Alexandre C.'s comment.

Related