44

我想知道程序员何时使用函数 try 块。什么时候有用?

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

输出:(在ideone

greater than zero
less than zero

编辑:因为有些人可能认为函数定义的语法不正确(因为语法看起来不熟悉),我不得不说它不是不正确的。它被称为函数尝试块。请参阅 C++ 标准中的 §8.4/1 [dcl.fct.def]。

4

6 回答 6

33

您可以在构造函数中使用它来捕获初始值设定项的错误。通常,您不会发现这些错误,因此这是一个非常特殊的用途。

否则,它是无用的:除非我被证明是错的,

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

严格等价于

void f() { try { ... } catch (...) { ... } }
于 2011-04-10T14:45:38.433 回答
14

函数 try 块在两种情况下对我很有用。

main()a) 围绕允许编写小型实用程序而不必担心本地错误处理有一个 catch all 子句:

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

这显然只是在main()自身内部有一个 try/catch 的语法糖。

b) 处理基类构造函数抛出的异常:

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

struct A : B {
     A() 
     try : B() { 
         // ... 
     } 
     catch (...) {
         // handle exceptions thrown from inside A() or by B() 
     } 
};
于 2011-04-10T14:51:36.403 回答
12

除了提到的功能用途之外,您还可以使用 function-try-block 为自己节省一层缩进。(Ack,关于编码风格的答案!)

通常,您会看到带有函数尝试块的示例,如下所示:

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

函数范围缩进到与没有函数尝试块相同的级别。这在以下情况下很有用:

  • 您有 80 个字符的列限制,并且必须在给定额外缩进的情况下换行。
  • 您正在尝试使用 try catch 改造某些现有功能,并且不想触及该功能的所有行。(是的,我们可以使用git blame -w.)

但是,对于完全用函数尝试块包装的函数,我建议不要在使用函数尝试块的某些函数和不在同一代码库中的某些函数之间交替。一致性可能比换行问题更重要。:)

于 2013-10-24T23:21:23.920 回答
7

关于函数 try 块如何操作的注意事项:

  • 对于构造函数,函数 try 块包含数据成员和基类的构造。

  • 对于析构函数,函数 try 块包含数据成员和基类的析构。它变得复杂,但对于 C++11,你必须noexcept(false)在你的析构函数(或基类/成员类的)声明中包含,否则任何破坏异常都将导致在 catch 块结束时终止。可以通过return在 catch 块中放置一条语句来防止这种情况发生(但这对构造函数不起作用)。

  • 构造函数或析构函数中的 catch 块必须抛出一些异常(否则它将隐式地重新抛出捕获的异常)return简单地(至少在构造函数的函数 catch 块中)是不合法的。但是请注意,您可以调用exit()或类似方法,这在某些情况下可能有意义。

  • catch 块不能返回值,因此它不适用于返回非 void 的函数(除非它们故意终止程序exit()或类似)。至少那是我读过的。

  • 构造函数尝试的 catch 块不能引用数据/基础成员,因为它们要么有 1) 未构造或 2) 在 catch 之前被破坏。因此,函数 try 块对于清理对象的内部状态没有用——当您到达那里时,该对象应该已经完全“死”了。 这个事实使得在构造函数中使用函数 try 块非常危险,因为如果你的编译器没有碰巧标记它,随着时间的推移很难监管这个规则。

有效(合法)用途

  • 将构造函数或其基/成员构造函数期间抛出的异常(转换为不同的类型/消息)转换。
  • 在析构函数或其基/成员析构函数期间翻译或吸收和抛出异常(尽管有析构函数礼仪)。
  • 终止程序(可能带有有用的消息)。
  • 某种异常日志记录方案。
  • 恰好需要完全封装的 try/catch 块的 void 返回函数的语法糖。
于 2015-02-24T00:04:22.613 回答
6

如果您想从构造函数的初始化程序中捕获异常,它可能会很有用。

但是,如果您确实以这种方式在构造函数中捕获异常,则必须重新抛出它或抛出新异常(即,您不能正常地从构造函数返回)。如果你不重新抛出,它只会隐式发生。

#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";
  }
}
于 2011-04-10T14:51:51.193 回答
4

您可以使用它们的另一件事是在调试期间以不干扰完成构建的方式提供额外数据。我还没有看到其他人使用或提倡它,但我觉得它很方便。

// 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 */

这将允许您在测试代码时获得有用的信息,并轻松地将其模拟出来而不影响任何事情。

于 2016-03-18T00:36:58.623 回答