9

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

Coder
  • 3,695
  • 7
  • 27
  • 42
  • Partial answer: all STL containers and operations have the `Basic Guarantee` as long as the pre-conditions in the Standard are respected (for example, destructors should not throw). – Matthieu M. Sep 13 '11 at 14:15
  • It is not very good, to throw exceptions during the construction. – Werolik Sep 13 '11 at 14:21
  • 4
    @Werolik: why not? I've always seen that as good. It's an essential component of RAII. – R. Martinho Fernandes Sep 13 '11 at 14:25
  • 3
    @Werolik: Wrong, really wrong. The whole standard (both the core language and the library) has been carefully designed for that. Error return values, on the other hand, are just not designed into C++. E.g. constructors cannot even return an error code. – MSalters Sep 13 '11 at 14:26
  • "what if this throws? " — Have you tried to answer this question yourself? What is your answer? – n. m. could be an AI Sep 13 '11 at 14:35
  • 3
    @n.m.: Then it might throw because of outofmemory, accessviolation etc.??? No idea what the status of the system after that is. Trashed heap, heap fragmentation, etc.? Basic guarantee? Strong guarantee? I can catch(..) outside my object, and clean, then retry, but no idea if system is still stable... And can't find where to read more about it, except source, which is not portable and pretty cryptic. – Coder Sep 13 '11 at 14:42
  • @R. Martinho Fernandes: And what do you have than? Half-constructed object? How to destroy it properly? – Werolik Sep 13 '11 at 14:52
  • 1
    The language guarantees that there is no such thing as an half-constructed object. But, *if you don't throw* that's exactly what you get. An object that is effectively constructed, *but not in an usable state*. – R. Martinho Fernandes Sep 13 '11 at 14:56
  • @Werolik: you'll have no object at all, and there will be nothing to destroy. – Konstantin Oznobihin Sep 13 '11 at 14:57
  • @R. Martinho Fernande: OK, you were right. I should have said "it's not very good to throw the exception in initialization list". – Werolik Sep 13 '11 at 15:02
  • @Martinho: strictly speaking, you'll have half-constructed object inside constructor itself, but it's a very different story then. – Konstantin Oznobihin Sep 13 '11 at 15:03
  • 1
    @Werolik: and then you'd be wrong again, objects in the initialization list are free to throw exceptions from their constructors as well. – Konstantin Oznobihin Sep 13 '11 at 15:04
  • 2
    @Werolik: All that I said *still applies*. Initialization lists don't change anything. – R. Martinho Fernandes Sep 13 '11 at 15:08
  • @Konstantin Oznobihin: Sutter doesn't think so. [Constructor Failures](http://www.gotw.ca/gotw/066.htm) – Werolik Sep 13 '11 at 15:20
  • @Werolik you may want to elaborate on why do you read that from that article. I read exactly the opposite. [Chat](http://chat.stackoverflow.com/rooms/3412/c-constructor-exceptions) is probably more appropriate to have a proper discussion though. – R. Martinho Fernandes Sep 13 '11 at 15:30
  • @R. Martinho Fernande: I didn't found anything contradicting my opinion there (if only you'd be more specific). Probably, you've just misunderstood what I've saird. There is surely no way to get half-constructed object after constructor finishes, and I doubt you'll find this term in standard, but it's a good enough name for an object state at some point in the middle of constructor and this state can be seen by other code if constructor passes 'this' somewhere, anyhow it doesn't really relate to throwing exceptions from constructor. – Konstantin Oznobihin Sep 13 '11 at 15:30
  • @Konstantin: this is getting confusing. That's not what I thought Werolik was talking about. And that's why I suggested chat. (not only that, but also the fact that some mod could swoop in at any moment and wipe this out.) – R. Martinho Fernandes Sep 13 '11 at 15:34
  • "it might throw because of outofmemory, accessviolation etc.??? No idea what the status of the system after that is. Trashed heap, heap fragmentation, etc.?" — is it any different from a situation when those same errors are reported by means of return codes? What are you going to do with E_THRASHED_HEAP? – n. m. could be an AI Sep 13 '11 at 15:42

5 Answers5

6

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.

Community
  • 1
  • 1
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • helpful, but please cite sources – Jason S Sep 13 '11 at 14:47
  • But there is a good chance that the "a.in" was created in the system directory, and that m_str trashed the stack? – Coder Sep 13 '11 at 14:50
  • @Coder: If "a.in" is created in a system directory (and streams have been set to throw on error) then it will throw an exception and nothing will go wrong. If you have a user interface you may want to catch this exception and report it to the user but otherwise it will be fine. – Martin York Sep 13 '11 at 15:05
  • @Coder: There is not chance that m_str will trash the stack. It will work or it will throw an exception if it fails. Unless there is a bug then the the stack will be fine. (Note if there are bugs you have no guarantees from any code). – Martin York Sep 13 '11 at 15:07
  • @Jason S: Anything in particular? – Martin York Sep 13 '11 at 15:07
3

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.

spraff
  • 32,570
  • 22
  • 121
  • 229
1

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.

Nicola Musatti
  • 17,834
  • 2
  • 46
  • 55
  • See: http://stackoverflow.com/questions/81656/where-do-i-find-the-current-c-or-c-standard-documents/4653479#4653479 Most of the common compilers are already well into implementing the features of the new standard. Though as you say it may be a while before they finish: http://gcc.gnu.org/projects/cxx0x.html – Martin York Sep 13 '11 at 15:19
1

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.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
0

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.

Cubic
  • 14,902
  • 5
  • 47
  • 92
  • 1
    "whether or not (and more importantly, which) exceptions are thrown is mostly left to the implementation." — Not true. There's nothing of this sort in the standard. – n. m. could be an AI Sep 13 '11 at 14:33
  • Streams can operate in 'exception mode' just fine. It just isn't the default mode – sehe Sep 13 '11 at 14:35
  • 2
    @n.m: see C++11 17.6.5.12/4: "Any other functions defined in the C++ standard library that do not have an exception-specification may throw implementation defined exceptions unless otherwise specified." The same was also present in C++03 17.4.4.8/3. – Konstantin Oznobihin Sep 13 '11 at 14:47
  • Yes, I forgot about thet. I wanted to say that library funtions are not allowed **not** to throw exceptions when the standard says they should. – n. m. could be an AI Sep 13 '11 at 15:30
  • Well, I guess, it's very similar to saying that it's mostly left to the implementation. – Konstantin Oznobihin Sep 13 '11 at 15:35