简体   繁体   中英

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 &) ? 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?

Also, if my class throws myexception , that is derived from exception, catch(exception &) seems to let it through. So that leaves me with catch(...) which IIRC catches access violation?.? 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? outofmemory_something, filenotfound_something? Where can I find the definitions of what STL objects can throw? 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. 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.
  • If ifstream throws in the body then m_str is destroyed and the object will never exist.

Should I wrap the code in 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?

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. So that leaves me with catch(...) which IIRC catches access violation?.? Is there another way?

If your exception is derived from std::exception then catch(std::exception&) will work. 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? outofmemory_something, filenotfound_something? Where can I find the definitions of what STL objects can throw? 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 .

So far, it seems that handling exceptions is like dancing in a big pile of 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. 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"
  • 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). 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. If you have something like a Foo::is_ok() test then what you have is a valid object which represents an invalid state.

  • 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.

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::at() does check, and throws.

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 .

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. 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. 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.

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. 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 ).

The exception guarantees from the standard library can be found in the ISO standard document (you have to pay a small fee for it).

Also, any good C++ book discusses exception safety at great length, the point being that usually you have nothing special to do. For instance, in your example, everything will be disposed properly, since ifstream close the file in its destructor.

AFAIK whether or not (and more importantly, which) exceptions are thrown is mostly left to the implementation. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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