9

可能重复:
函数 try 块何时有用?
函数的 try-catch 语法之间的区别

此代码在 class 内int构造对象时引发异常。异常被普通块捕获,代码输出:DogUseResourcesinttry-catch

Cat()  
Dog()  
~Cat()  
Inside handler

#include <iostream>
using namespace std;

class Cat
{
    public:
    Cat() { cout << "Cat()" << endl; }
    ~Cat() { cout << "~Cat()" << endl; }
};

class Dog
{
    public:
    Dog() { cout << "Dog()" << endl; throw 1; }
    ~Dog() { cout << "~Dog()" << endl; }
};

class UseResources
{
    class Cat cat;
    class Dog dog;

    public:
    UseResources() : cat(), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

int main()
{
    try
    {
        UseResources ur;
    }
    catch( int )
    {
        cout << "Inside handler" << endl;
    }
}

现在,如果我们将UseResources()构造函数的定义替换为使用 a的定义function try block,如下所示,

UseResources() try : cat(), dog() { cout << "UseResources()" << endl; } catch(int) {}

输出是一样的

Cat()  
Dog()  
~Cat()  
Inside handler 

即,最终结果完全相同。

那么, a 的目的是function try block什么?

4

2 回答 2

12

想象一下如果UseResources是这样定义的:

class UseResources
{
    class Cat *cat;
    class Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { delete cat; cat = NULL; cout << "~UseResources()" << endl; }
};

如果Dog::Dog()抛出,cat则将泄漏内存。BecaseUseResources的构造函数从未完成,该对象从未完全构造。因此它没有调用它的析构函数。

为了防止这种泄漏,您必须使用函数级 try/catch 块:

UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...)
{
  delete cat;
  throw;
}

为了更全面地回答您的问题,构造函数中函数级 try/catch 块的目的是专门进行这种清理。函数级的 try/catch 块不能吞下异常(普通的可以)。如果他们抓住了东西,当他们到达 catch 块的末尾时,他们会再次抛出它,除非你用 明确地重新抛出它throw。您可以将一种类型的异常转化为另一种类型,但您不能只是吞下它并像没有发生一样继续前进。

这是为什么应该使用值和智能指针而不是裸指针的另一个原因,即使作为类成员也是如此。因为,在你的情况下,如果你只有成员值而不是指针,你不必这样做。是使用裸指针(或不在 RAII 对象中管理的其他形式的资源)迫使这种事情发生。

请注意,这几乎是函数 try/catch 块的唯一合法使用。


更多不使用函数 try 块的理由。上面的代码被巧妙地破坏了。考虑一下:

class Cat
{
  public:
  Cat() {throw "oops";}
};

那么,在UseResources's 的构造函数中会发生什么?好吧,new Cat显然,表达式会抛出。但这意味着它cat从未被初始化。这意味着这delete cat将产生未定义的行为。

您可以尝试通过使用复杂的 lambda 而不仅仅是new Cat

UseResources() try
  : cat([]() -> Cat* try{ return new Cat;}catch(...) {return nullptr;} }())
  , dog()
{ cout << "UseResources()" << endl; }
catch(...)
{
  delete cat;
  throw;
}

这在理论上解决了这个问题,但它打破了假设的不变量UseResources. 也就是说,这UseResources::cat将始终是一个有效的指针。如果这确实是 的不变量UseResources,那么此代码将失败,因为它允许构造 ,UseResources尽管有异常。

基本上,除非new Catnoexcept(显式或隐式),否则无法使此代码安全。

相比之下,这总是有效的:

class UseResources
{
    unique_ptr<Cat> cat;
    Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

简而言之,将函数级 try-block 视为严重的代码异味。

于 2011-12-02T16:50:24.460 回答
2

普通函数 try 块的用途相对较小。它们几乎与体内的 try 块相同:

int f1() try {
  // body
} catch (Exc const & e) {
  return -1;
}

int f2() {
  try {
    // body
  } catch (Exc const & e) {
    return -1;
  }
}

唯一的区别是 function-try-block 位于稍大的 function-scope 中,而第二个构造位于 function-body-scope 中——前一个范围只看到函数参数,后者也看到局部变量(但这不会影响 try 块的两个版本)。

唯一有趣的应用程序来自构造函数-try-block:

Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { }

这是可以捕获来自初始化程序之一的异常的唯一方法。您无法处理异常,因为整个对象构造仍然必须失败(因此无论您是否愿意,您都必须以异常退出 catch 块)。但是,它专门处理初始化列表中的异常的唯一方法。

这有用吗?可能不是。构造函数 try 块与以下更典型的“初始化为空并赋值”模式之间基本上没有区别,这本身就很糟糕:

Foo() : p1(NULL), p2(NULL), p3(NULL) {
  p1 = new Bar;
  try {
    p2 = new Zip;
    try {
      p3 = new Gulp;
    } catch (...) {
      delete p2;
      throw;
    }
  } catch(...) {
    delete p1;
    throw;
  }
}

如您所见,您遇到了无法维护、无法扩展的混乱局面。构造函数尝试块会更糟,因为您甚至无法判断已经分配了多少指针。所以真的只有当你有两个可泄漏的分配时才有用。 更新:感谢阅读这个问题,我注意到实际上你根本不能使用 catch 块来清理资源,因为引用成员对象是未定义的行为。所以[结束更新]

简而言之:没用。

于 2011-12-02T17:11:16.813 回答