简体   繁体   English

我在哪里可以找到有关C ++ / STL方法异常保证的信息?

[英]Where can I find information about C++/STL method Exception guarantees?

I was writing code with exception handling the other day, and I had a few questions about exceptions, their guarantees and throwables. 前几天我正在编写带有异常处理的代码,我有一些关于异常,它们的保证和抛出的问题。

Basically, say you have: 基本上,说你有:

class X {
string m_str;
X() : m_str("foo")//what if this throws?
{
    ifstream b("a.in")//what if this throws?
}

And after running through all the articles I could find, I still have no idea what is the clean way to handle this. 在浏览了我能找到的所有文章之后,我仍然不知道处理这个问题的干净方法是什么。

Say I have a code like: 说我有一个像这样的代码:

{
    ...
    X myInstanceOfClassX;
    ...
}

Should I wrap the code in catch(exception &) ? 我应该将代码包装在catch(exception &)吗? And if I do that, does string and ifstream guarantee a strong guarantee, that no resources are leaked and nothing has been left half opened? 如果我这样做, stringifstream保证有一个强有力的保证,没有资源泄露,什么都没有被打开一半?

Also, if my class throws myexception , that is derived from exception, catch(exception &) seems to let it through. 此外,如果我的类抛出myexception ,即派生自异常, catch(exception &)似乎让它通过。 So that leaves me with catch(...) which IIRC catches access violation?.? 所以这让我陷入了IIRC捕获访问权限的问题catch(...) Is there another way? 还有另外一种方法吗?

Then there was a piece of information somewhere that any exception thrown in subconstructors of object constructor shouldn't be caught, and constructor should throw in case any of the member objects throw. 然后在某处有一条信息,不应该捕获在对象构造函数的子构造函数中抛出的任何异常,并且构造函数应该抛出以防任何成员对象抛出。

And what if the code above would have been called not from constructor, but from regular a function void foo() , which exceptions should I catch? 如果上面的代码不是从构造函数调用,而是从常规函数void foo()调用,我应该捕获哪些异常呢? outofmemory_something, filenotfound_something? outofmemory_something,filenotfound_something? Where can I find the definitions of what STL objects can throw? 我在哪里可以找到STL对象可以抛出的定义? Are they implementation specific? 它们的实施是否具体?

Where is the authoritative source where I could clear all my doubts and questions on this topic? 权威来源在哪里可以清除我对此主题的所有怀疑和疑问?

So far, it seems that handling exceptions is like dancing in a big pile of gooo. 到目前为止,处理异常似乎就像在一大堆gooo中跳舞一样。 Error codes seem A LOT simpler and safer... 错误代码似乎更简单,更安全......

If either of these throw 如果其中任何一个抛出

class X {
string m_str;
X() : m_str("foo")//what if this throws?
{
    ifstream b("a.in")//what if this throws?
}

Then the object you were creating will not exist. 然后您创建的对象将不存在。
If an exception is thrown in the constructor of an object, then all fully created members are destructed (using the their destructor) and memory for the object is returned to the system. 如果在对象的构造函数中抛出异常,则会破坏所有完全创建的成员(使用其析构函数),并将对象的内存返回给系统。 Thus any members that are not fully constructed at the throw point will not be destroyed (as they have not been created). 因此,任何未在抛出点完全构造的构件都不会被销毁(因为它们尚未被创建)。

  • If m_str() throws in the initializer list then the object will never exist. 如果m_str()抛出初始化列表,那么该对象将永远不存在。
  • If ifstream throws in the body then m_str is destroyed and the object will never exist. 如果ifstream抛入正文,则m_str将被销毁,并且该对象将永远不存在。

Should I wrap the code in catch(exception &)? 我应该将代码包装在catch(exception&)中吗? And if I do that, does string and ifstream guarantee a strong guarantee, that no resources are leaked and nothing has been left half opened? 如果我这样做,字符串和ifstream是否保证有一个强有力的保证,没有资源泄露,什么都没有被打开一半?

Even if you catch an exception (outside the object) there is no object to work on as it never existed (the object only starts its lifespan after the constructor completes). 即使你捕获异常(在对象之外)也没有对象可以工作,因为它从未存在过(对象只在构造函数完成后才开始其生命周期)。

In the above you are guaranteed there are no leaks or open resources. 在上面,您可以保证没有泄漏或开放资源。

Also, if my class throws myexception, that is derived from exception, catch(exception &) seems to let it through. 此外,如果我的类抛出myexception,即派生自异常,catch(exception&)似乎让它通过。 So that leaves me with catch(...) which IIRC catches access violation?.? 所以这让我陷入了IIRC捕获访问权限的问题(?)? Is there another way? 还有另外一种方法吗?

If your exception is derived from std::exception then catch(std::exception&) will work. 如果您的异常是从std :: exception派生的,则catch(std::exception&)将起作用。 If it is not working then you are doing something wrong (but we need more detail (like the code that throws and the code that catches, an English description is not adequate)). 如果它不起作用那么你做错了(但我们需要更多的细节(比如抛出的代码和捕获的代码,英文描述是不够的))。

Then there was a piece of information somewhere that any exception thrown in subconstructors of object constructor shouldn't be caught, and constructor should throw in case any of the member objects throw. 然后在某处有一条信息,不应该捕获在对象构造函数的子构造函数中抛出的任何异常,并且构造函数应该抛出以防任何成员对象抛出。

Probably the best option and as a general rule not bad advice. 可能是最好的选择,作为一般规则也不错的建议。

And what if the code above would have been called not from constructor, but from regular a function void foo(), which exceptions should I catch? 如果上面的代码不是从构造函数调用,而是从常规函数void foo()调用,我应该捕获哪些异常呢? outofmemory_something, filenotfound_something? outofmemory_something,filenotfound_something? Where can I find the definitions of what STL objects can throw? 我在哪里可以找到STL对象可以抛出的定义? Are they implementation specific? 它们的实施是否具体?

You should only catch exceptions if you can do something about it. 如果你可以做一些事情,你应该只捕获例外。 Usually this is nothing so don;t catch them let the application quit normally (via the exception unwinding the stack). 通常这没什么,所以不要抓住它们让应用程序正常退出(通过异常展开堆栈)。

Where is the authoritative source where I could clear all my doubts and questions on this topic? 权威来源在哪里可以清除我对此主题的所有怀疑和疑问?

You're question are so varied that that is hard. 你的问题是如此多变以至于难以实现。
I could recommend "Exceptional C++" by Herb Sutter . 我可以推荐Herb Sutter的“Exceptional C ++”

So far, it seems that handling exceptions is like dancing in a big pile of gooo. 到目前为止,处理异常似乎就像在一大堆gooo中跳舞一样。 Error codes seem A LOT simpler and safer... 错误代码似乎更简单,更安全......

You are wrong there. 你错了。 Exceptions are much easier. 例外情况要容易得多。 You just seem to be over-thinking it and getting confused. 你似乎只是过度思考它而感到困惑。 That is not to say that error codes do not have their place. 这并不是说错误代码没有它们的位置。

If something goes wrong and you can not fix it locally then throw an exception. 如果出现问题and you can not fix it locally则抛出异常。 All the classes in the standard are designed with exception in mind and will behave correctly. 标准中的所有类都是在设计时考虑到例外并且行为正确。 So that just leaves your classes. 这样就离开了你的班级。

Rules of thumb: (for your objects) 经验法则:(适用于您的对象)

  • Make sure your classes clean themselves up in the destructor 确保您的类在析构函数中清理自己
  • If your object contains resources make sure the "rule of 3 is obeyed" 如果您的对象包含资源,请确保遵守“3规则”
  • Never have more than one resource per object. 每个对象永远不会有多个资源。
    Note: You can have multiple things like std::string or std::ifstream as they are the ones controlling the resource (they each control one resource so your class is not controlling the resource). 注意:你可以有多个东西,比如std :: string或std :: ifstream,因为它们是控制资源的东西(它们各自控制一个资源,所以你的类不控制资源)。 A resource (in this context) is something that you must manually create/destroy. 资源(在此上下文中)是您必须手动创建/销毁的东西。

That's it, the rest auto-magically works. 就是这样,其余的自动神奇地起作用。

Every function has a precondition and a postcondition. 每个函数都有前提条件和后置条件。 The correct time to throw an exception is when the postconditions cannot be satisfied. 抛出异常的正确时间是无法满足后置条件的时间。 There is no other correct time. 没有其他正确的时间。

There are two special cases. 有两种特殊情况。

  • The postcondition for a constructor is the existence of a valid object , therefore throwing is the only reasonable way to report an error. 构造函数的后置条件是存在有效对象 ,因此throw是报告错误的唯一合理方法。 If you have something like a Foo::is_ok() test then what you have is a valid object which represents an invalid state. 如果您有类似Foo::is_ok()测试,那么您拥有的是一个有效的对象,它代表一个无效的状态。

  • The postcondition for a destructor is the nonexistence of an object , therefore throwing is never a reasonable way to report an error. 析构函数的后置条件是对象不存在 ,因此抛出永远不是报告错误的合理方法。 If you have something tricky to do at the end of an object's life, do it as a separate Foo::commit() member function call. 如果你在对象的生命结束时有一些棘手的事情要做,那就把它做成一个单独的Foo::commit()成员函数调用。

Beyond this you have options, and it's a matter of taste. 除此之外,你有选择,这是一个品味问题。

For example 例如

  • std::vector::operator[] does not check preconditions and is noexcept(true) , but std::vector::operator[]不检查前置条件,并且是noexcept(true) ,但是
  • std::vector::at() does check, and throws. std::vector::at()会检查并抛出。

The choice is whether or not you assume your preconditions are valid. 选择是您是否认为您的先决条件是有效的。 In the first case you are using design-by-contract. 在第一种情况下,您使用的是按合同设计。 In the second case, given that you have detected that they are not, you know the postconditions cannot be valid and therefore should throw; 在第二种情况下, 因为你已经发现,他们都没有,你知道后置条件不能有效的,因此应该抛出; in the first case you assume they are and, given that, the postconditions must be valid and therefore you never need to. 在第一种情况下,你假设它们是,并且, 鉴于此,后置条件必须是有效的,因此你永远不需要。

GOTW covers a lot of the dark corners of exceptions and demonstrates nicely why things are what they are . GOTW涵盖了很多异常的黑暗角落,并很好地展示了为什么事情就是这样

The only authoritative reference on how the standard library works, including under which conditions it is allowed to throw which exception types, is the C++ Language Standard. 关于标准库如何工作的唯一权威参考,包括允许抛出哪些异常类型的条件,是C ++语言标准。 Years ago it was available in electronic form for a reasonable price, but unfortunately this doesn't appear to be the case anymore. 几年前它以合理的价格以电子形式提供,但不幸的是,这似乎不再是这种情况。 You might consider searching the Standard Committee site for drafts, but there will obviously be differences with the published standard. 您可以考虑在标准委员会网站上搜索草稿,但显然会与已发布的标准存在差异。

Note also that a new edition of the standard has just been publish and it will take some time before vendors implement the new features with reasonable completeness. 另请注意,该标准的新版本刚刚发布,供应商需要一些时间才能以合理的完整性实现新功能。

Throwing exceptions in constructors is a good idea, since you have no other means of reporting failure. 在构造函数中抛出异常一个好主意,因为您没有其他报告失败的方法。

I tend to prefer C++ exceptions to error codes, even when they are considered "control flow", because I don't have to add checks everywhere. 我倾向于更喜欢错误代码的C ++异常,即使它们被认为是“控制流”,因为我不必在任何地方添加检查。 But this is a debatable matter of taste. 但这是一个有争议的品味问题。 For constructors, you have no choice however. 对于构造函数,你别无选择。

As soon as a constructor throws an exception, all the subobjects which were initialized get destroyed, and if the object was constructed via operator new , the corresponding operator delete gets called. 一旦构造函数抛出异常,所有初始化的子对象都会被销毁,如果对象是通过operator new构造的,则会调用相应的operator delete

Note that when a constructor throws, the object cannot be used: 请注意,当构造函数抛出时,无法使用该对象:

my_class a; // If this throws, everything past this line is not accessible.
            // Therefore, you cannot use a.

or 要么

my_class* b;

try
{
    b = new my_class; // If this throws, ...
}
catch (...)
{
    // b has undefined state here (but no memory is leaked)
}

So if you only use proper RAII objects, you are safe and have nothing to do except letting the exception propagate. 因此,如果您只使用正确的RAII对象,那么除了让异常传播之外,您是安全的并且无所事事。 If however you manually retrieve a disposable resource, then you may have to clean it up and rethrow the exception: 但是,如果您手动检索一次性资源,则可能需要清理它并重新抛出异常:

template <typename T>
struct my_vector
{
    // This is why it is not advisable to roll your own vector.
    my_vector(size_t n, const T& x)
    {
        begin = static_cast<T*>(custom_allocator(n * sizeof(T)));
        end = begin + n;

        size_t k = 0;
        try
        {
            // This can throw...
            for (; k != n; k++) new(begin + k) T(x); 
        }
        catch (...)
        {
            // ... so destroy everything and bail out
            while (--k) (begin + k)->~T();
            custom_deallocator(begin);
            throw;
        }
    }

private:
    T* begin;
    T* end;
};

but this should be quite rare if you use proper RAII objects (a quick grep from my current codebase shows hundreds of throw , but only two catch ). 但是如果你使用正确的RAII对象,这应该是非常罕见的(从我当前的代码库快速grep显示数百个throw ,但只有两个catch )。

The exception guarantees from the standard library can be found in the ISO standard document (you have to pay a small fee for it). 标准库的例外保证可以在ISO标准文档中找到(您只需支付少量费用)。

Also, any good C++ book discusses exception safety at great length, the point being that usually you have nothing special to do. 此外,任何优秀的C ++书籍都会详细讨论异常安全问题,关键是通常你没有什么特别的事情要做。 For instance, in your example, everything will be disposed properly, since ifstream close the file in its destructor. 例如,在您的示例中,所有内容都将正确处理,因为ifstream在其析构函数中关闭文件。

AFAIK whether or not (and more importantly, which) exceptions are thrown is mostly left to the implementation. 无论是否(更重要的是,哪些)异常被抛出,AFAIK主要留给实现。 I don't see any point in attempting to catch those - I mean, what are you gonna do if this fails? 我没有看到试图捕捉那些的任何意义 - 我的意思是,如果失败你会怎么做? Is there any reasonable way to recover from an exception being thrown there? 有没有合理的方法从被抛出的异常中恢复?

Keep in mind that no exception is thrown if, for example, a file can't be opened - that would simply lead to the stream being set to a fail state. 请记住,例如,如果无法打开文件,则不会引发异常 - 这只会导致流被设置为失败状态。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM