需要在构造函数中进行 try catch

发布于 2024-11-30 01:37:14 字数 507 浏览 0 评论 0原文

链接 http://gotw.ca/gotw/066.htm 指出

道德#1:构造函数尝试块处理程序只有一个目的——转换异常。 (也许还可以进行日志记录或其他一些副作用。)它们对任何其他目的没有用处。

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

如果构造函数抛出异常,则不会运行该对象的析构函数。如果您的对象已经完成了需要撤消的操作(例如分配一些内存、打开文件或锁定信号量),则必须由对象内的数据成员记住此“需要撤消的操作”。

这2种说法不矛盾吗?第一种暗示构造函数中的 try catch 几乎没有用,而第二种则表示需要释放资源。我在这里缺少什么?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

醉生梦死 2024-12-07 01:37:14

道德#1 谈论function-try-block &第二个声明谈论的是普通的try catch块,两者明显不同。

您需要了解两者之间的区别才能理解这两个句子的意义。 这个在这里回答解释说。

Moral #1 talks about function-try-block & the second statement talks about a normal try catch block, both are distinctly different.

You need to understand the different between the two to understand how the two sentences make sense. This answer here explains that.

殊姿 2024-12-07 01:37:14

它们指的是不同的事物。

第一个与函数 try 块相关,即包含整个函数的 try 块,并且在构造函数的情况下,还包括对基类和成员对象的构造函数,即在实际构造函数主体运行之前执行的内容。

其精神是,如果基类或类成员无法正确构造,则对象构造必须失败并出现异常,因为否则新构造的对象将处于与基类不一致的状态对象/成员对象已构建一半。因此,此类 try 块的目的必须只是转换/重新抛出此类异常,也许会记录事件。您不能以任何其他方式执行此操作:如果您没有在 catch 中显式 throw ,则隐式 throw; 会由编译器以防止“半构造对象”的可能性。

第二个是指在你的构造函数体内引发的异常;在这种情况下,它表示您应该使用“常规”try 块来捕获异常,释放到目前为止分配的资源,然后重新抛出,因为不会调用析构函数。

请注意,这种行为是有道理的,因为析构函数的隐式契约(与任何其他非构造函数的成员函数一样)是它期望以一致的状态处理对象;但是构造函数抛出异常意味着该对象尚未完全构造,因此会违反此约定。

They refer to different things.

The first one is related to the function-try block, i.e. a try block that includes a whole function, and, in the case of constructors, includes also the calls to the constructors of the base classes and of the member objects, i.e. stuff that is executed before the actual constructor body is run.

The morale is that, if a base class or a class member fails to constructs correctly, the object construction must fail with an exception, because otherwise your newly constructed object is left in an inconsistent state, with the base object/member objects half constructed. So, the purpose of such try block must be only to translate/rethrow such exception, maybe logging the incident. You can't do in any other way: if you don't explicitly throw inside your catch, an implicit throw; is added by the compiler to prevent the "half-constructed object" eventuality.

The second one refers to exceptions that are risen inside the body of your constructor; in this case, it says that you should use a "regular" try block to catch the exception, free the resources you have allocated up to now and then rethrow, because the destructor won't be called.

Notice that this behavior makes sense, since the implicit contract for the destructor, as for any other member function that is not the constructor, is that it expects to work on an object in a consistent state; but an exception thrown from the constructor means that the object hasn't been fully constructed yet, so this contract would be violated.

多情癖 2024-12-07 01:37:14

对于简单术语的模糊翻译是:函数尝试块只能用于翻译异常并且始终使用RAII,并且每个资源应该由一个单个对象,并且它们并不矛盾。哦,好吧,翻译并不完全是这样,但论证最终得出了这两个结论。

来自 C++FAQ lite 的第二句话指出,对于构造函数未完成的对象,不会调用析构函数。这反过来意味着,如果您的对象正在管理资源,尤其是当它管理多个资源时,您就会陷入大麻烦。您可以在异常逃逸构造函数之前捕获该异常,然后尝试释放已获取的资源,但为此您需要知道实际分配了哪些资源。

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

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

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

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

我们可以首先分析简单的情况:常规 try 块。 first 中的构造函数将两个指针初始化为 null(初始化列表中的:p()、q()),然后尝试为这两个对象创建内存。在这两个新类型之一中,会引发异常并进入 catch 块。 什么new失败了?我们不在乎,如果是第二个new失败,那么delete实际上会释放p< /代码>。如果是第一个,因为初始化列表首先将两个指针设置为 0 并且在空指针上调用 delete 是安全的,所以 delete p<如果第一个新操作失败,/code> 是一个安全的操作。

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

回到我的松散翻译,如果您使用 RAII 并且每个对象仅一个资源,我们会将类型编写为:

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

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

因此,我们甚至不需要 try 来管理资源...这给我们留下了 Sutter 提到的其他用法:翻译异常。虽然我们必须抛出失败的构造函数,但我们可以选择抛出什么。我们可能决定抛出一个自定义的initialization_error,无论构造中的内部故障是什么。这就是函数 try 块的用途:

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

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();
   }
};
探春 2024-12-07 01:37:14

这两种说法不矛盾吗?

不。第二个基本上意味着如果构造函数抛出一个来自它(即构造函数)的异常,那么析构函数不会被调用。第一个意味着 function-try-block 不会让异常从构造函数中出来。它在构造函数本身内部捕获它,并在那里处理它。

Are these 2 statements not contradictory?

No. The second one basically means if the constructor throws an exception which comes out of it (i.e the constructor), then the destructor doesn't get called. And the first one means that function-try-block doesn't let the exception come out of the constructor. It catches it inside the constructor itself, and handles it right there.

无敌元气妹 2024-12-07 01:37:14

首先,构造函数 try-block 与构造函数中的 try-catch 不同。

其次,说“需要撤消的事情必须由数据成员记住”与说“使用 try-catch 块来撤消事情”不同。

这两种说法并不矛盾,只是说的是不同的事情。事实上,第二个根本不是在谈论 try-catch。

First, a constructor function-try-block isn't the same as a try-catch within a constructor.

Second, saying "stuff that needs to be undone must be remembered by a data member" is not the same as saying, "use try-catch blocks to undo things".

There's no contradiction between the two statements, they're talking about different things. In fact, the second one isn't talking about try-catch at all.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文