When is a function try block useful?

2019-01-09 02:39发布

问题:

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.

回答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 (...) { ... } }


回答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() 
     } 
};


回答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. :)



回答4:

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";
  }
}


回答5:

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.


回答6:

Another thing you can use them for is to provide extra data during debugging, in a manner that doesn't interfere with the finished build. I haven't seen anyone else use or advocate it, but it's something I find convenient.

// Function signature helper.
#if defined(_WIN32) || defined(_WIN64)
    #define FUNC_SIG __FUNCSIG__
#elif defined(__unix__)
    #define FUNC_SIG __PRETTY_FUNCTION__
// Add other compiler equivalents here.
#endif  /* Function signature helper. */

void foo(/* whatever */)
#ifdef     DEBUG
try
#endif  /* DEBUG */
{
    // ...
}
#ifdef     DEBUG
catch(SomeExceptionOrOther& e) {
    std::cout << "Exception " << e.what() << std::endl
              << "* In function: " << FUNC_SIG << std::endl
              << "* With parameters: " << /* output parameters */ << std::endl
              << "* With internal variables: " << /* output vars */ << std::endl;

    throw;
}
#endif  /* DEBUG */

This would allow you to both obtain useful information while testing your code, and easily dummy it out without affecting anything.