Exception

I dont understand try-catch-throw thing..

Please somebody explain it to me

Thanks..
1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
     std::cout << "Let's try to do something!\n";

     try //Try block, anything here will be Executed
     {
          int *value = new int //"new" throws an exception if it fails to allocate memory
     }
     catch(...) //This block is to "catch" exceptions IF they occur
     {
          std::cout << "Failed to allocate memory!";
     }
}
Last edited on
The topic is error handling.

You are a function that should do something. This time you got input that makes your thing impossible.

For example, you are sqrt(). You were called with -4.72. You can't compute the root, because you don't operate with complex numbers. What shall you do?

If you return a number like 42, the user will think that 42*42 == -4.72. That is not right.

You could simply crash the entire program, but that will not make the user happy.

What you should do is to somehow send message to the calling code and let the calling code decide how to handle the error.


Since the convention is that the root is positive, you could decide (and document) that you will return a negative value if input is negative. The user can check the result. Some functions cannot use that, because all possible values could be valid results.

You could use by reference parameter to return the error/success double sqrt( double value, T& result );. Extra parameters are not convenient.

The sqrt() in the standard library has actually two methods.
One writes the error on global variable. Caller can check that.
The other throws exception. Caller can check that.
You have to very good examples above.

I thought I'd point these few things out about exceptions.

To be clear, as you see from these two discussions, throwing is what happens in the code where the error happens.

"Trying" happens in code where the code expects an error might occur, and catching happens just after the "try" (they're a pair, always), to catch what is thrown.

One key feature, and part of the purpose as depicted by it's first "inventor" in this language, is to handle serious errors in a location in the code far removed from where the error happened.

Typically, errors are simple. A file isn't found, for example. For such a situation it is simple to imagine that the function which opens the file should be able to return some value that indicates the error. This could just as easily be that a function searches for a code of some kind, say it's a measurement like inches or meters, but when that code can't be found it should be simple for the function that does the search should return special "not found" value.

There are, however, errors that are a bit more serious. Often such errors happen when a function calls a function (which calls a function...), and deep inside that nested series of actions something happens like a failure of memory, the disconnection of a cable to the Internet (or loss of WiFi), or perhaps the limit of a particular size has been reached (which is like an out of memory situation), but reporting that error isn't going to be easy. If the problem happened deep inside this series of functions calling functions, the code that called for this is so far removed from where the error happens that it is difficult to design an error value that is easily returned. It could even be something as serious as accessing memory outside of the "legal" limits, commonly known as a buffer overrun, which can generate a serious operating system fault.

For those situations it is important to be able to send a signal that such a serious error has occurred, and handling it may be difficult or impossible, and the point at which it is handled is far removed from where the problem happened.

This is the nature of the design of exceptions.

It is typically accepted that exceptions should be used for "exceptional errors", not simple errors. There are plenty of examples where this design suggestion is ignored, and simple errors are "thrown". Exceptions are a bit "heavy" for simple errors.

One reason for this is what a throw actually does. It is related to a very old "C" concept called a long jump. Computers "jump" around code frequently. Every pair of brackets that enclose code, like in an "if" or a "for" loop are position where code jumps around. These are "short" jumps that happen inside a function.

A long jump is much more complicated. This is the kind of jump which can violate the boundary of a function. Simple jumps don't do that unless they are returning from a function. A return is a special kind of jump which, rather obvious, returns from where it came. Still, the brackets which enclose the function are the points implied for jumping into and out of the function.

Long jumps, on the other hand, can perform jumps beyond function boundaries (even several nested functions and recursive functions). Such jumps are "wired" to be able to deal with this kind of jumping. If you think of a function that calls a function, there must be two "returns" to get back out of those two nested functions. For a long jump, however, a special "marker" is made which allows one single jump to cross these nested boundaries correctly.

The exception does this "under the hood". Throwing causes the code to jump from inside nested functions into the catch. The catch statements are the target destinations for the throw, way outside any function boundaries. They're quite heavy and burdensome compared to simple returns.

However, by the time a serious error occurs for which exceptions are designed to handle, all the code which may be entirely skipped by such a long jump probably can't operate properly anyway.

Importantly, the "try" statement informs the compiler to prepare for this carefully. Any local allocations (objects formed locally inside functions) are recognized as part of the problem - they must be "cleared" when a throw causes this long jump from deep inside nested functions. The "try" sets that up so that when a "throw" causes this long jump, all of the local objects "on the stack" (created locally in those functions) are properly cleared and destroyed.

The intention is that when it is possible to recover, they program is running in a sane, rational state (without leaks). Sometimes exceptions, while they should be serious errors, can be "fixed" and the software can continue. More often, however, the situation is so severe that the only real solution is to exit the program, and the "catch" statements are an opportunity to attempt to save work (perhaps to a file) that can still be saved, and perhaps to do that in a special "error recovery" way.

You may have used software that fails, but asks if it could send and error report to the manufacturer of the software. Sometimes when you run professional software, like a word processor or spreadsheet, you may have noticed they can tell if they crashed the last time you ran it, offering to recover your file(s). It is likely they used exceptions to trap the error when it happened, and in the "catch" wrote some special configuration information they use to help recover the work, and offer to send reports for bug fixing or generally "apologize" for the problem.





Another example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
#include <string>
#include <exception>

struct ExceptionBadFormat : public std::exception {
    std::string s;
    ExceptionBadFormat(const std::string& s) : s("BadFormt: " + s) { }
    const char* what () const throw () { return s.c_str(); }
};

void func2(int n) {
    switch (n) {
    case 1: throw 42;
    case 2: throw std::string("hello");
    case 3: throw std::invalid_argument("that's just not right");
    case 4: throw ExceptionBadFormat("those are some bad bytes");
    case 5: throw 4.2;
    }
    std::cout << "exiting func2\n"; // this doesn't print if func2 throws
}


void func1(int n) {
    func2(n);  // the throw in func2 "throws" the exception up the call chain,
               // first to func1, but since func1 has no catch clause, the
               // exception passes up the call chain to main
    std::cout << "exiting func1\n"; // this doesn't print if func2 throws
}

int main() {
    for (;;) {
        try {
            std::cout << "0:nothrow  1:int  2:string  3:exception  "
                         "4:our exception  5:other  9:quit\n:: ";
            int n;
            std::cin >> n;
            std::cin.ignore();
            if (n == 9) break;
            std::cout << "--\n";
            func1(n);
            std::cout << "func1 didn't throw\n"; // skipped if func1 throws
        }
        catch (int n) {
            std::cerr << "error: int: " << n << '\n';
        }
        catch (const std::string& s) {
            std::cerr << "error: string: " << s << '\n';
        }
        catch (const ExceptionBadFormat& e) {
            std::cerr << "error: " << e.what() << '\n';
        }
        catch (const std::invalid_argument& e) {
            std::cerr << "error: invalid_argument: " << e.what() << '\n';
        }
        catch (const std::exception& e) { // catch any exception derived from std::exception
            std::cerr << "error: exception: " << e.what() << '\n';
        }
        catch (...) {  // will catch any type
            std::cerr << "error: unknown\n";
        }

        std::cout << "--\n";
    }
}


It should be noted that an exception can be handled by a function somewhere in the call stack and then it can be "rethrown" (or a different one thrown) to be handled again higher up.
Last edited on
Topic archived. No new replies allowed.