需要在构造函数中尝试catch [英] Need for try catch within a constructor

查看:164
本文介绍了需要在构造函数中尝试catch的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

链接 http://gotw.ca/gotw/066.htm 说明



道德#1:构造函数 - try-block处理程序只有一个目的 - 翻译异常。



虽然 http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8



如果构造函数抛出异常,对象的析构函数不会运行。如果你的对象已经做了一些需要被撤销的东西(比如分配一些内存,打开一个文件或锁定一个信号量),这个需要被撤消的东西必须由对象内的数据成员记住。 / p>

这两个语句不矛盾吗?第一种意味着在构造函数中的try catch几乎没有用,而第二种说它需要释放资源。

解决方案

一个模糊翻译成纯粹的术语将是: function-try-blocks只能用于翻译异常始终使用RAII,每个资源应由单个对象管理,并且它们不矛盾。



第二个引用,来自C ++ FAQ lite表示,析构函数不会被其构造函数未完成的对象调用。这反过来意味着,如果你的对象管理资源,更重要的是当它管理多个,你是在深深的麻烦。您可以在转义构造函数之前捕获异常,然后尝试释放您获取的资源,但这样做您需要知道哪些资源实际分配了。



第一个引述说,构造函数中的一个函数try块必须抛出(或 rethrow ),所以它的有用性是非常有限的,特别是它能做的唯一有用的事情是翻译异常。请注意,函数try块的唯一原因是在初始化列表执行期间捕获异常



,是函数try块不是处理第一个问题的方法吗?



好吧...不是真的。考虑一个有两个指针并在其中存储内存的类。并且考虑第二个调用可能失败,在这种情况下,我们将需要释放第一个块。我们可以尝试以两种不同的方式实现它,一个函数try块或一个常规的try块:

  //常规try块//函数try块
struct test {
type * p;
type * q;
test():p(),q(){test()try:p(new int),q(new int){
try {
p = new type;
q = new type;
} catch(...){} catch(...){
delete p;删除p;
throw;
}} //异常被抛出这里
}
〜test(){delete p;删除q; }
};

我们可以先分析简单的情况:一个常规的try块。第一个中的构造函数将初始化列表中的两个指针初始化为null(:p(),q()两个对象的内存。在两个新类型之一中抛出异常并且输入catch块。 新失败了吗?我们不在乎,如果是第二个失败,那么删除实际上会释放 p 。如果是第一个,因为初始化列表首先设置两个指针 0 ,可以安全地调用 delete 在空指针上,如果第一个新的失败, delete p 是一个安全操作。



在右边。我们已经将资源的分配移动到初始化列表,因此我们使用一个函数try块,这是捕获异常的唯一方式。再次,一个消息失败。如果第二个失败, delete p 将释放在该指针中分配的资源。但是如果是第一个新的失败,则 p 从未被初始化,并且调用 delete p 未定义的行为。



回到我的松散翻译,如果你使用RAII,每个对象只有一个资源, :

  struct test {
std :: auto_ptr< type> p,q;
test():p(new type),q(new type)
{}
};

在这个修改的例子中,因为我们使用RAII,所以我们不关心异常。如果第一个 new 抛出,没有资源被获取,没有发生任何事情。如果第二次抛出失败,则 p 将被销毁,因为 p 在尝试第二个 new 之前(有序列点),并且资源将被释放。



我们甚至不需要尝试来管理资源...这使我们与Sutter提到的其他用法:翻译异常。虽然我们必须抛出失败的构造函数,我们可以选择我们抛出的东西。我们可能会决定,我们想抛出一个自定义的 initialization_error ,无论在构造中是什么内部故障。这是函数try块可以用于:

  struct test {
std :: auto_ptr< type& p,q;
test()try:p(new type),q(new type){
} catch(...){
throw initialization_error
}
};


The link http://gotw.ca/gotw/066.htm states that

Moral #1: Constructor function-try-block handlers have only one purpose -- to translate an exception. (And maybe to do logging or some other side effects.) They are not useful for any other purpose.

While http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.8

If a constructor throws an exception, the object's destructor is not run. If your object has already done something that needs to be undone (such as allocating some memory, opening a file, or locking a semaphore), this "stuff that needs to be undone" must be remembered by a data member inside the object.

Are these 2 statements not contradictory? The first one kind of implies that the try catch within a constructor is pretty much useless while the second says that it is needed to free resources. What am i missing here?

解决方案

A vague translation to plain terms would be: function-try-blocks can only be used to translate exceptions and always use RAII and each resource should be managed by a single object, and they do not contradict. Oh, well, the translation is not exactly that, but the argument eventually leads to those two conclusions.

The second quote, from the C++FAQ lite states that the destructor will not be called for an object whose constructor did not complete. That in turn means that if your object is managing resources, and more so when it manages more than one, you are in deep trouble. You can catch the exception before it escapes the constructor, and then try to release the resources that you have acquired, but to do so you would need to know which resources were actually allocated.

The first quote says that a function try block within a constructor must throw (or rethrow), so the usefulness of it is very limited, and in particular the only useful thing that it can do is translate the exception. Note that the only reason for the function try block is to catch an exception during the execution of the initialization list.

But wait, is the function try block not a way to handle the first problem?

Well... not really. Consider a class that has two pointers and stores memory in them. And consider that the second call might fail, in which case we will need to release the first block. We could try to implement that in two different ways, with a function try block, or with a regular try block:

// regular try block                       // function try block
struct test {
   type * p;
   type * q; 
   test() : p(), q() {                     test() try : p( new int ), q( new int ) {
      try {
          p = new type;
          q = new type;
      } catch (...) {                      } catch (...) {
          delete p;                            delete p;
          throw;
      }                                    } // exception is rethrown here
   }
   ~test() { delete p; delete q; }
};

We can analyze first the simple case: a regular try block. The constructor in the first initializes the two pointers to null (: p(), q() in the initialization list) and then tries to create the memory for both objects. In one of the two new type an exception is thrown and the catch block is entered. What new failed? We do not care, if it was the second new that failed, then that delete will actually release p. If it was the first one, because the initialization list first set both pointers to 0 and it is safe to call delete on a null pointer, the delete p is a safe operation if the first new failed.

Now on the example on the right. We have moved the allocation of the resources to the initialization list, and thus we use a function try block, which is the only way of capturing the exception. Again, one of the news fail. If the second new failed, the delete p will free the resource allocated in that pointer. But if it was the first new that failed, then p has never been initialized, and the call to delete p is undefined behavior.

Going back to my loose translation, if you used RAII and only one resource per object, we would have written the type as:

struct test {
   std::auto_ptr<type> p,q;
   test() : p( new type ), q( new type )
   {}
};

In this modified example, because we are using RAII, we do not really care about the exception. If the first new throws, no resource is acquired and nothing happens. If the second throw fails, then p will be destroyed because p has been fully constructed before the second new is attempted (there is sequence point there), and the resource will be released.

So we do not even need a try to manage the resources... which leaves us with the other usage that Sutter mentioned: translating the exception. While we must throw out of the failing constructor, we can choose what we throw. We might decide that we want to throw a custom made initialization_error regardless of what was the internal failure in the construction. That is what the function try block can be used for:

struct test {
   std::auto_ptr<type> p,q;
   test()  try : p( new type ), q( new type ) {
   } catch ( ... ) {
      throw initialization_error();
   }
};

这篇关于需要在构造函数中尝试catch的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆