There is a lot of controversy on the web about exception handling in c++. This article will introduce the use of exception handling in c++, should we use exception handling, and what to be aware of when using exception handling.

What is exception handling

Exception handling, of course, refers to the handling of exceptions. Exceptions are problems that occur during the execution of a program that does not follow a properly conceived process, such as dividing by zero. Exception handling provides a way to transfer control of the program, involving three key words:

  • Throw: When a problem occurs, the program throws an exception
  • Catch: Catch exceptions through the catch keyword where a throw might want to handle a problem
  • Try: The code in the try block identifies the specific exception to be activated, usually followed by one or more catch blocks

Look directly at the sample code:

void func() { throw exception; } int main() {try {// put the code that may throw the exception inside the try. The code inside the block is called the protection code func(); } catch (exception1&e) {// catch (exception2&e) {// catch (exception2&e) { Exception type: Exception2 // code} catch (...) { // code } return 0; }Copy the code

What’s wrong with the c++ standard

C++ provides a standard set of exceptions defined in C++ that we can use in our programs. They are organized in a parent-child hierarchy as follows:

Pictures from the rookie tutorial

The specific exception should not need special introduction, see the English name can know the general meaning.

Custom exception

You can customize exceptions by inheriting and overloading the Exception class, as shown in the code:

#include <stdexcept>
class MyException : public std::runtime_error {
public:
  MyException() : std::runtime_error("MyException") { }
};
void f()
{
   // ...
   throw MyException();
}

int main() {
    try {
        f();
    } catch (MyException& e) {
        // ...
    } catch (...) {

    }
    return 0;
}Copy the code

Should we use exceptions

In c++, there has been controversy about whether to use exceptions. The typical example is that Chen shuo said that exceptions should not be used on zhihu, and Google and the us department of defense have clearly defined coding specifications to prohibit the use of exceptions in c++. Here I have found a lot of Chinese and English materials and listed some in the reference links at the end of this article.

The discussion about whether or not use exceptions posts here, www.zhihu.com/question/22…

I will not post what Chen Shuo said, his level is high without doubt, but he said some things are still very controversial, about exception processing, quoted teacher Wu Yongwei said: “Chen Shuo is of course a technical bull. However, when it comes to programming languages, I’m more willing to trust Bjarne Stroustrup, Herb Sutter, Scott Meyers, and Andrei Alexandrescu. These gurus agree that exceptions are a better way of handling errors than returning error codes.”

There are historical burden and Google specifically disable exception, they also agree that the exception handling is a better way than error code, but they have no choice, because the compiler of exception handling is bad, they project has a lot of the exception safety code, if all change to exception handling code is a lot of work, See the links above and some of the links I cited at the end of this article.

The dod disabled exceptions for real time performance, toolchain does not guarantee real time performance when an exception is thrown, but dod disabled many c++ features, such as memory allocation, are we really looking for high performance like airplanes?

You can guess my conclusion from the above, of course, it is not my conclusion, but the conclusion of the big guys: exception handling is recommended.

There are some potential downsides to exception handling:

  • It has a limited impact on application performance, but normal workflow is as fast or faster than normal functions when no exceptions are thrown
  • It can cause applications to become 10-20% bigger, but do we really care about the size of applications (except on mobile)

The benefits of exception handling over error codes:

  • If you do not use trycatch, you will need to return an error code, which will inevitably increase the ifelse statement, which will increase the judgment overhead each time the function returns. If you can eliminate trycatch, the code may be more robust, as shown in the following example:

    void f1() { try { // ... f2(); / /... } catch (some_exception& e) { // ... code that handles the error... } } void f2() { ... ; f3(); . ; } void f3() { ... ; f4(); . ; } void f4() { ... ; f5(); . ; } void f5() { ... ; f6(); . ; } void f6() { ... ; f7(); . ; } void f7() { ... ; f8(); . ; } void f8() { ... ; f9(); . ; } void f9() { ... ; f10(); . ; } void f10() { // ... if ( /*... some error condition... */ ) throw some_exception(); / /... }Copy the code

    Instead of using error codes:

    int f1() { // ... int rc = f2(); if (rc == 0) { // ... } else { // ... code that handles the error... } } int f2() { // ... int rc = f3(); if (rc ! = 0) return rc; / /... return 0; } int f3() { // ... int rc = f4(); if (rc ! = 0) return rc; / /... return 0; } int f4() { // ... int rc = f5(); if (rc ! = 0) return rc; / /... return 0; } int f5() { // ... int rc = f6(); if (rc ! = 0) return rc; / /... return 0; } int f6() { // ... int rc = f7(); if (rc ! = 0) return rc; / /... return 0; } int f7() { // ... int rc = f8(); if (rc ! = 0) return rc; / /... return 0; } int f8() { // ... int rc = f9(); if (rc ! = 0) return rc; / /... return 0; } int f9() { // ... int rc = f10(); if (rc ! = 0) return rc; / /... return 0; } int f10() { // ... if (... some error condition...) return some_nonzero_error_code; / /... return 0; }Copy the code

    Error code is very troublesome for the reverse transmission of the problem, resulting in code swelling. If there is a link in the middle that is forgotten or incorrectly processed, it will lead to the generation of bugs. Exception processing is more concise for the error processing, which can be more convenient to feed back the error information to the caller. There is no need for the caller to use an additional ifelse branch to handle successful or unsuccessful cases.

  • In general, error codes are used to indicate whether the function was successfully executed. One value indicates that the function was successfully executed, and one or more values indicate that the function failed. Different error codes indicate different error types, and the caller needs to use multiple ifELSE branches to handle different error types. If there are more IfElse, then more test cases must be written, and more effort must be spent, leading to project delays.

    Take numerical code for example:

    class Number {
    public:
      friend Number operator+ (const Number& x, const Number& y);
      friend Number operator- (const Number& x, const Number& y);
      friend Number operator* (const Number& x, const Number& y);
      friend Number operator/ (const Number& x, const Number& y);
      // ...
    };Copy the code

    The simplest can be called like this:

    void f(Number x, Number y) {
      // ...
      Number sum  = x + y;
      Number diff = x - y;
      Number prod = x * y;
      Number quot = x / y;
      // ...
    }Copy the code

    But if an error needs to be handled, such as division by zero or overflow, the function gets the wrong result and the caller needs to deal with it.

    Let’s look at the way error codes are used:

    class Number { public: enum ReturnCode { Success, Overflow, Underflow, DivideByZero }; Number add(const Number& y, ReturnCode& rc) const; Number sub(const Number& y, ReturnCode& rc) const; Number mul(const Number& y, ReturnCode& rc) const; Number div(const Number& y, ReturnCode& rc) const; / /... }; int f(Number x, Number y) { // ... Number::ReturnCode rc; Number sum = x.add(y, rc); if (rc == Number::Overflow) { // ... code that handles overflow... return -1; } else if (rc == Number::Underflow) { // ... code that handles underflow... return -1; } else if (rc == Number::DivideByZero) { // ... code that handles divide-by-zero... return -1; } Number diff = x.sub(y, rc); if (rc == Number::Overflow) { // ... code that handles overflow... return -1; } else if (rc == Number::Underflow) { // ... code that handles underflow... return -1; } else if (rc == Number::DivideByZero) { // ... code that handles divide-by-zero... return -1; } Number prod = x.mul(y, rc); if (rc == Number::Overflow) { // ... code that handles overflow... return -1; } else if (rc == Number::Underflow) { // ... code that handles underflow... return -1; } else if (rc == Number::DivideByZero) { // ... code that handles divide-by-zero... return -1; } Number quot = x.div(y, rc); if (rc == Number::Overflow) { // ... code that handles overflow... return -1; } else if (rc == Number::Underflow) { // ... code that handles underflow... return -1; } else if (rc == Number::DivideByZero) { // ... code that handles divide-by-zero... return -1; } / /... }Copy the code

    Let’s look at how exception handling can be used:

    void f(Number x, Number y) { try { // ... Number sum = x + y; Number diff = x - y; Number prod = x * y; Number quot = x / y; / /... } catch (Number::Overflow& exception) { // ... code that handles overflow... } catch (Number::Underflow& exception) { // ... code that handles underflow... } catch (Number::DivideByZero& exception) { // ... code that handles divide-by-zero... }}Copy the code

    If there are more operations, or more error codes, the advantage of exception handling is even greater.

  • Using exceptions makes the code logic clearer, sets the code out in the correct logic, makes it easier to read, and puts error handling to the end.

  • Exceptions can optionally be handled by themselves or passed on to upper-level handlers

Key points for exception handling

  1. What should I not do with exception handling?

    • Throw is simply used to throw an error, indicating that the function is not executing as expected
    • Use catch to catch errors, such as conversion types or memory allocation failures, only when you know you can handle them
    • Instead of using throws to throw coding errors, use assert or other methods to tell the compiler or crash process to collect debug information
    • If there is an event that must crash, or a problem that cannot be recovered, you should not use a throw because it cannot be handled externally and should crash the program
    • A try or catch should not be used simply to return a value from a function. A return value should use a return operation. A catch should not be used, as this is misleading to programmers
  2. Exception handling may seem simple and easy to use, but it requires project members to adhere strictly to development specifications and decide when to use exceptions and when not to use them, rather than using both exceptions and error codes.

  3. Can a constructor throw an exception? Can and suggests using exceptions, because the constructor has no return value, so can only throw an exception, there is another way is to add a member variable identifies whether the object structure is successful, this approach that will add a return to the return value of function, if define an array of objects that you need for each object determine whether array structure is successful, This code is not very good.

  4. Can a constructor throw an exception and cause a memory leak? No, the constructor throws an exception that causes a memory leak. That’s a compiler bug, fixed in the 21st century. Don’t listen to rumors.

    void f() {
      X x;             // If X::X() throws, the memory for x itself will not leak
      Y* p = new Y();  // If Y::Y() throws, the memory for *p itself will not leak
    }Copy the code
  5. Never throw an exception in a destructor, like an array of objects. If one of the objects throws an exception during the destructor, the rest of the objects cannot be destructed. Instead, the destructor catches the exception and swallows it or terminates the program.

  6. What if the constructor throws an exception after applying for a resource? Using smart Pointers, you can also use STD ::string instead for char*.

    #include <memory>
    
    using namespace std;
    
    class SPResourceClass {
    private:
        shared_ptr<int> m_p;
        shared_ptr<float> m_q;
    public:
        SPResourceClass() : m_p(new int), m_q(new float) { }
        // Implicitly defined dtor is OK for these members,
        // shared_ptr will clean up and avoid leaks regardless.
    };Copy the code
  7. Always throw an exception by value passing and catch it by reference passing.

  8. You can throw primitive types, you can throw objects, you can throw anything

  9. catch(…) All exceptions can be caught

  10. Implicit type conversions are not triggered during a catch procedure

  11. The exception is thrown, but STD ::terminate() is raised until main is not caught.

  12. Unlike Java, c++ does not enforce checking for exceptions, and an outer throw will compile without a catch

  13. When an exception is thrown, all local objects between the try and throw are destructed before the catch

  14. If a member function does not raise any exceptions, you can use the noexcept keyword

  15. An exception can be rethrown with a throw

    int main() 
    { 
        try { 
            try  { 
                throw 20; 
            } 
            catch (int n) { 
                 cout << "Handle Partially "; 
                 throw;   //Re-throwing an exception 
            } 
        } 
        catch (int n) { 
            cout << "Handle remaining "; 
        } 
        return 0; 
    }Copy the code

quiz

Do you really understand exception handling? We can do a few quizzes:

Let’s see what this output looks like:

Test code 1:

#include <iostream> 
using namespace std; 

int main() 
{ 
   int x = -1; 

   // Some code 
   cout << "Before try \n"; 
   try { 
      cout << "Inside try \n"; 
      if (x < 0) 
      { 
         throw x; 
         cout << "After throw (Never executed) \n"; 
      } 
   } 
   catch (int x ) { 
      cout << "Exception Caught \n"; 
   } 

   cout << "After catch (Will be executed) \n"; 
   return 0; 
} Copy the code

Output:

Before try
Inside try
Exception Caught
After catch (Will be executed)Copy the code

The code after the throw is not executed

Test code 2:

#include <iostream> 
using namespace std; 

int main() 
{ 
    try  { 
       throw 10; 
    } 
    catch (char *excp)  { 
        cout << "Caught " << excp; 
    } 
    catch (...)  { 
        cout << "Default Exception\n"; 
    } 
    return 0; 
}Copy the code

Output:

Default ExceptionCopy the code

The 10 thrown does not match a char*, and the catch(…) All exceptions can be caught.

Test code 3:

#include <iostream> 
using namespace std; 

int main() 
{ 
    try  { 
       throw 'a'; 
    } 
    catch (int x)  { 
        cout << "Caught " << x; 
    } 
    catch (...)  { 
        cout << "Default Exception\n"; 
    } 
    return 0; 
}Copy the code

Output:

Default ExceptionCopy the code

‘a’ is a character and cannot be converted implicitly to an int, so it still matches… In the.

Test code 4:

#include <iostream> 
using namespace std; 

int main() 
{ 
    try  { 
       throw 'a'; 
    } 
    catch (int x)  { 
        cout << "Caught "; 
    } 
    return 0; 
}Copy the code

The program crashes because the exception thrown until main is not caught, STD ::terminate() is called to terminate the program.

Test code 5:

#include <iostream> 
using namespace std; 

int main() 
{ 
    try { 
        try  { 
            throw 20; 
        } 
        catch (int n) { 
             cout << "Handle Partially "; 
             throw;   //Re-throwing an exception 
        } 
    } 
    catch (int n) { 
        cout << "Handle remaining "; 
    } 
    return 0; 
}Copy the code

Output:

Handle Partially Handle remainingCopy the code

A throw in a catch rethrows an exception.

Test code 6:

#include <iostream> using namespace std; class Test { public: Test() { cout << "Constructor of Test " << endl; } ~Test() { cout << "Destructor of Test " << endl; }}; int main() { try { Test t1; throw 10; } catch(int i) { cout << "Caught " << i << endl; }}Copy the code

Output:

Constructor of Test
Destructor of Test
Caught 10Copy the code

Local variables in the try and throw are destructed before the thrown exception is caught.

A small summary

Exception handling is more concise for error handling, making it easier to send error information back to the caller without requiring the caller to use an extra IFelse branch to handle successful or unsuccessful cases. If you don’t care very much about real-time performance or how big your program is, you can use exception handling instead of the error code that you normally use in C.

C++ exception handling is introduced here, you understand?

The resources

www.zhihu.com/question/22…

isocpp.org/wiki/faq/

Docs.microsoft.com/en-us/cpp/c…

Blog.csdn.net/zhangyifei2…

www.runoob.com/cplusplus/c…

www.geeksforgeeks.org/exception-h…

For more articles on effective c++, check out my vx official number: program cat, welcome to talk.