Is exception handling still disabled by your c + + team?

There are many disputes about c + + exception handling on the Internet. This article will introduce the use of c + + exception handling, should we use exception handling, and what should be paid attention to when using exception handling.

What is exception handling

Exception handling, of course, refers to the handling of exceptions. Exceptions refer to the problems that occur during the execution of a program, which do not go down according to the correctly conceived process, such as the operation of dividing by zero. Exception handling provides a way to transfer control of a program, which involves three key words:

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

See the example code directly:

void func() {
    throw exception; // Throw exception
}

int main() {
    try { // try to put code that may throw an exception. The code in the block is called protection code
        func();
    } catch (exception1& e) { // Exception caught, exception type exception1
        // code
    } catch (exception2& e) { // Exception caught, exception type exception2
        // code
    } catch (...) {
        // code
    }
    return 0;
}

What are the exceptions of c + + standard

C + + provides a series of standard exceptions, which are defined in < exception > and can be used in programs. They are organized in a parent-child hierarchy, as follows:

<img src="D:\wzq\markdown\exceptions_in_cpp.png" alt="deadlock" style="zoom:80%;" />

Picture from rookie tutorial

The specific exception should not need to be introduced. You can know the general meaning by looking at the English name.

Custom exception

You can define 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;
}

Should we use exceptions

There are always disputes about whether to use exceptions in c + +. The typical problem is that Chen shuoshi said that exceptions should not be used. In addition, google and the U.

The discussion about whether to use exceptions is here, https://www.zhihu.com/question/22889420

I won't post what Chen Shuo said. There's no doubt about his high level, but some of what he said are still controversial. As for exception handling, I quote Wu Yongwei's teacher: "Chen Shuo is certainly a technical bull. However, I prefer to trust Bjarne Stroustrup, Herb Sutter, Scott Meyers and Andrei Alexandrescu in programming languages. These gods all think that exceptions are a better way to handle errors than to return error codes. "

However, google has a historical burden to explicitly disable exceptions. They also agree that exception handling is a better way to handle exceptions than error codes, but they have no choice. Because previous compilers did not handle exceptions well, they already have a large number of non exception safe codes in their projects. If all the codes are changed into exception handling codes, there will be a lot of work, which can be seen in detail The links to the faces and some of the links I quoted at the end of this article.

The U.S. Department of defense disables exceptions for real-time performance. The tool chain cannot guarantee the real-time performance when the program throws exceptions. However, the Department of defense disables many c + + features, such as memory allocation. Do we really pursue the same high-performance as the aircraft?

Through the above introduction, you can guess my conclusion. Of course, this is not my conclusion, but the conclusion of the bigwigs: exception handling is recommended.

Exception handling has some potential disadvantages:

  • It will have a limited impact on the performance of the program, but in normal workflow, when no exception is thrown, it will be as fast or even faster as ordinary functions
  • It will cause the program volume to increase by 10% - 20%, but do we really care about the volume of the program (except for the mobile end)

The advantages of exception handling over error codes:

  • If you don't use trycatch, you need to use the method of returning error codes, which will inevitably increase the ifelse statement. Each time a function returns, it will increase the cost of judgment. If you can eliminate trycatch, the code may be more robust. For 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();
      // ...
    }
    

    The error code is used:

    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;
    }
    

    The error code method is very troublesome for the reverse transmission of the problem, resulting in code swelling. If there is a link in the middle that forgets to handle or handles the problem wrongly, it will lead to the generation of bug s. Exception handling is more concise for error handling, and it is more convenient to feed the error information back to the caller. At the same time, the caller does not need to use additional ifelse branches to handle successful or unsuccessful situations.

  • Generally speaking, error code is used to indicate whether the function is executed successfully. One value indicates that the function is executed successfully. Another or more values indicate that the function fails to execute. Different error codes indicate different error types. The caller needs to use multiple ifelse branches for different error types. If there are more ifelse, it is necessary to write more test cases and spend more energy, leading to the project evening line.

    Take numerical operation 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);
      // ...
    };
    

    The simplest can be called as follows:

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

    But if you need to deal with errors, such as dividing by 0 or overflowing a value, the function will get the wrong result, and the caller needs to deal with it.

    First look at how to use error codes:

    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;
      }
      // ...
    }
    

    Let's see how to use exception handling:

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

    If there are more operations, or more error codes, the advantage of exception handling will be more obvious.

  • Using exceptions can make the code logic clearer, list the code according to the correct logic, make the logic closer, and make the code easier to read and understand, while error handling can be put at the end of the process alone.

  • Exceptions can be handled by themselves or passed to the upper level for handling

Key points of exception handling

  1. What should not be done with exception handling?

    • Throw is only used to throw an error. The identification function is not executed as expected
    • Use catch to catch errors, such as conversion types or memory allocation failures, only if you know you can handle them
    • Do not use throw to throw coding errors. Use assert or other methods to tell the compiler or crash process to collect the debug information
    • If there is an event that must crash, or an unrecoverable problem, throw should not be used, because throwing it out can't be handled, and the program should crash
    • try and catch should not be used for function return value simply. Return operation should be used for function return value instead of catch. This will cause misunderstanding to programmers, and at the same time, exceptions should not be used to jump out of the loop
  2. Exception handling seems to be simple and easy to use, but it requires project members to strictly abide by the development specifications, determine when to use exceptions and when not to use them, rather than using both exceptions and error codes.

  3. Can the constructor throw an exception? It is also recommended to use exceptions. Because the constructor does not return a value, it can only throw exceptions. Another way is to add a member variable to identify whether the object has been constructed successfully. In this way, an additional function that returns the return value will be added. If you define an array of objects, you need to judge whether each object of the array has been constructed successfully The code is not very good.

  4. Does the constructor throw an exception that causes a memory leak? No, the constructor throws an exception to generate a memory leak, which is a compiler bug that has been 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
    }
    
  5. Never throw an exception in the destructor, or take an object array as an example. There are multiple objects in the array. If an exception is thrown in the process of destructing one of the objects, the remaining objects will not be destructed. The destructor should catch the exception and swallow them or terminate the program instead of throwing them.

  6. How to throw an exception after applying for resources in the constructor? With smart pointers, you can also use std::string 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.
    };
    
  7. Always throw an exception with throw by value passing and catch by reference passing.

  8. You can throw basic types or objects. You can throw anything

  9. catch(...) can catch all exceptions

  10. Implicit type conversion will not be triggered during catch

  11. The exception is thrown, but until the main function is not caught, it will std::terminate()

  12. Unlike java, c + + does not force exceptions to be checked. Even if there is no catch, the outer layer will be compiled

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

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

  15. Throw can re throw the exception

    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; 
    }
    

quiz

Do you really understand the exception handling? We can do several test questions:

See what these pieces of code will output:

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; 
} 

Output:

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

The code after throw will not be 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; 
}

Output:

Default Exception

The throw out 10 does not match char * first, and catch(...) can catch all exceptions.

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; 
}

Output:

Default Exception

'a' is a character and cannot be implicitly converted to int, so it still matches in.

Test code 4:

#include <iostream> 
using namespace std; 
  
int main() 
{ 
    try  { 
       throw 'a'; 
    } 
    catch (int x)  { 
        cout << "Caught "; 
    } 
    return 0; 
}

The program crashes because the exception thrown is not caught until the main function, and 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; 
}

Output:

Handle Partially Handle remaining

throw in catch throws the exception again.

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; 
  } 
} 

Output:

Constructor of Test
Destructor of Test
Caught 10

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

Summary

Exception handling is more concise for error handling, and it is more convenient to feed error information back to the caller. At the same time, the caller does not need to use additional ifelse branches to handle successful or unsuccessful situations. If we don't pay special attention to the real-time performance or the size of the program, we can use exception handling instead of the error code handling in the c language we usually use.

About c + + exception handling introduced here, do you know?

reference material

https://www.zhihu.com/question/22889420

https://isocpp.org/wiki/faq/

https://docs.microsoft.com/en-us/cpp/cpp/errors-and-exception-handling-modern-cpp?view=vs-2019

https://blog.csdn.net/zhangyifei216/article/details/50410314

https://www.runoob.com/cplusplus/cpp-exceptions-handling.html

https://www.geeksforgeeks.org/exception-handling-c/

<effective c++> More articles, please pay attention to my princess V X: program meow, welcome to communicate~

Tags: Programming Google Mobile Java

Posted on Wed, 20 May 2020 03:33:25 -0400 by johnnyblotter