C++ Exception Handling
整理自:Thinking in C++, Volume 2
There are two basic models in exception-handling theory: (1) termination and (2) resumption.
- C++ and Java both support termination, which means there’s no way to automatically resume execution at the point where the exception occurred.
 - Using resumption semantics means that the exception handler is expected to do something to rectify the situation, and then the faulting code is automatically retried, presuming success the second time.
 
1. Throwing
You can use any type (including built-in types) as an exception when you throw, but usually you’ll create special classes for throwing exceptions.
而对应的 catch 也可以用 primitive type,比如:
#include <iostream>
using namespace std;
void oz() {
    throw 47; // throwing an int as an exception
}
int main() {
    try {
        oz();
    } catch(int) { // catching an int exception 
        cout << "Something wrong..." << endl;
    }
}
2. Catching All
如果想像 Java 的 catch(Exception e) 一样来抓所有的 exception,C++ 需要写成 catch (...):
catch(...) {
	// catch any type of exception
}
The ellipsis gives you no possibility to have an argument, so you can’t know anything about the exception or its type. It’s a “catch-all.” Such a catch clause is often used to clean up some resources and then re-throw the exception.
3. Re-throwing
You re-throw an exception by using throw with no argument inside a handler:
catch(...) {
	// Deallocate your resource here, and then re-throw
	throw;
}
In addition, everything about the exception object is preserved, so the handler at the higher context that catches the specific exception type can extract any information the object may contain.
4. When an exception is never caught
If no handler at any level catches the exception, the special library function terminate() (declared in the <exception> header) is automatically called. By default, terminate() calls the Standard C library function abort(), which abruptly exits the program.
You can install your own terminate() function using the standard set_terminate() function, which returns a pointer to the terminate() function you are replacing (which will be the default library version the first time you call it), so you can restore it later if you want. Your custom terminate() must take no arguments and have a void return value. In addition, any terminate() function you install must not throw an exception.
书上还有一种写法要注意下:在 destructor 中 throw 一个 exception 是不可能被 catch 到的,所以会直接到 terminate() 手上。所以 destructor 中必须是不能 throw 的。
5. Exceptions during construction
If an exception is thrown inside a constructor, the associated destructor will not be called for that object. 对这个 object 而言,这并不是内存泄露,因为这个 object 并没有被创建成功。
但是如果这个 object 的 constructor 内有 new,就需要考虑下面两种状况:
- 如果 
T的 constructor 内有一个new A,但是抛了异常(不管是系统的bad_alloc或者是A自己的operator new抛的异常)- 对 
A而言这不是系统泄露(因为没有创建成功) - 对 
T而言也不是系统泄露(因为也没有创建成功) 
 - 对 
 - 如果 
T的 constructor 内new A成功,紧接着一个new B抛了异常而T的 constructor 内没有catch- 对 
B而言这不是系统泄露(因为没有创建成功) - 对 
T而言也不是系统泄露(因为也没有创建成功) - 但是 
A没有人回收(因为一般delete是写在T的 destructor,而这时 destructor 不会执行),对A而言就是系统泄露 
 - 对 
 
为了避免状况 (2),有两种方法可以使用:
- 在
T的 constructor 内catchB的异常,然后delete已经分配的A - 对 
A和B单独再建两个 classResA和ResBnew A写在ResA的 constructor 里,delete A写在ResA的 destructor 里new B写在ResB的 constructor 里,delete B写在ResB的 destructor 里- 其实就是把 
A、B的new、delete封装成了ResA、ResB的生命周期- Using this approach, each allocation becomes atomic, by virtue of being part of the lifetime of a local object.
 - 这么做的原因是 local object 是可以被 stack unwinding 过程回收的,而指针无法被 stack unwinding 回收.
            
- In the C++ exception mechanism, control moves from the 
throwstatement to the firstcatchstatement that can handle the thrown type. When thecatchstatement is reached, all of the automatic variables that are in scope between thethrowandcatchstatements are destroyed in a process that is known as stack unwinding. - 再次考虑状况 (2):如果 
T的 constructor 内ResA创建成功,紧接着创建ResB时抛了异常而T的 constructor 内没有catch。这时只要T的 constructor 外有catch,就进入 stack unwinding- 这对 
ResB而言不是系统泄露(因为没有创建成功) - 对 
T而言也不是系统泄露(因为也没有创建成功) ResA被 stack unwinding 销毁,不存在系统泄露。- 这整个过程完全不依赖 
T的 destructor 
 - 这对 
 
 - In the C++ exception mechanism, control moves from the 
 - This technique is called Resource Acquisition Is Initialization (RAII for short)
 
 
 
ResA 和 ResB 可以用	auto_ptr 实现。auto_ptr 是 #include <memory> 自带的 RAII wrapper for pointers. auto_ptr<A> 就相当于上面的 ResA。
The auto_ptr class template also overloads the pointer operators * and -> to forward these operations to the original pointer the auto_ptr object is holding. So you can use the auto_ptr object as if it were a raw pointer.
~~~~~~~~~~ 2015-05-21 补充;来自 Item 13, Effective C++ ~~~~~~~~~~
Because an auto_ptr automatically deletes what it points to when the auto_ptr is destroyed, it’s important that there never be more than one auto_ptr pointing to an object. If there were, the object would be deleted more than once, and that would put your program on the fast track to undefined behavior. To prevent such problems, auto_ptrs have an unusual characteristic: copying them (via copy constructor or copy assignment operator) sets them to null, and the copying pointer assumes sole ownership of the resource!
std::auto_ptr<Investment> pInv1(createInvestment()); 
std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the object; pInv1 is now null
pInv1 = pInv2; 							// now pInv1 points to the object, and pInv2 is null
STL containers require that their contents exhibit “normal” copying behavior, so containers of auto_ptr aren’t allowed.
Both auto_ptr and tr1::shared_ptr use delete in their destructors, not delete []. That means that using them with dynamically allocated arrays is a bad idea, though, regrettably, one that will compile. (vector and string can almost always replace dynamically allocated arrays.)
~~~~~~~~~~ 2015-05-21 补充完毕 ~~~~~~~~~~
~~~~~~~~~~ 2024-12-11 补充:auto_ptr is deprecated ~~~~~~~~~~
See C++11 Smart Pointer: auto_ptr is deprecated. Use other smart pointers.
~~~~~~~~~~ 2024-12-11 补充完毕 ~~~~~~~~~~
6. Function-level try-blocks
所谓 function–level try-block,就是把整个 function body 当做 try-block。那 try 写在哪儿?写在 function name 和 { 中间:
int main() try {
	throw "main";
} catch(const char* msg) {
	cout << msg << endl;
	return 1;
}
简直是奇行种…… C++ allows function-level try-blocks for any function.
注意书上还有一种写法是把 try 写在 constructor initializer list 前面:
Ext(int i) try : Base(i) { // Base 的 constructor 可能抛异常
	// Ext construction goes here
} catch(BaseException&) {
	throw ExtException("Base constructor fails");;
}
7. Standard Exceptions
- All standard exception classes derive ultimately from the class 
exception, defined in the header<exception>. - The two main derived classes are 
logic_errorandruntime_error, which are found in<stdexcept>(which itself includes<exception>).- The class 
logic_errorrepresents errors in programming logic (which could presumably be detected by inspection)- such as passing an invalid argument.
 
 runtime_errors are those that occur as the result of unforeseen forces (which can presumably be detected only when the program executes)- such as hardware failure or memory exhaustion.
 - divide-by-zero 在 C++ 中并不是一个 
runtime_error 
 - The class 
 - Both 
runtime_errorandlogic_errorprovide a constructor that takes astd::stringargument so that you can store a message in the exception object and extract it later withexception::what(). std::exceptiondoes not provide a constructor that takes astd::stringargument, so you’ll usually derive your exception classes from eitherruntime_errororlogic_error.- The iostream exception class 
ios::failureis derived fromexception. 
- The iostream exception class 
 
extends logic_error | 
      Usage | 
|---|---|
domain_error | 
      Reports violations of a precondition. | 
invalid_argument | 
      Indicates an invalid function argument. | 
length_error | 
      Indicates an attempt to produce an object whose length is greater than or equal to npos (the largest representable value of context’s size type, usually std::size_t). | 
    
out_of_range | 
      Reports an out-of-range argument. | 
bad_cast | 
      Thrown for executing an invalid dynamic_cast expression in RTTI. | 
    
bad_typeid | 
      Reports a null pointer p in an expression typeid(*p). | 
    
extends runtime_error | 
      Usage | 
|---|---|
range_error | 
      Reports violation of a postcondition. | 
overflow_error | 
      Reports an arithmetic overflow. | 
bad_alloc | 
      Reports a failure to allocate storage. | 
8. Declaring throw
标准用词是 exception specification,懂了就好。注意语法:
// 注意是 throw 不是 throws
void f() throw(FooException, BarException, BazException); // 可能抛出三种异常
void f();				// 可能抛出任何异常
void f() throw();		// 不会抛出任何异常
void f() throw(...);	// ERROR. no such syntax
If your exception specification claims you’re going to throw a certain set of exceptions and then you throw something that isn’t in that set, the special function unexpected() is called. The default unexpected() calls the terminate() function. 你也可以用 set_unexpected() 自己注册一个,语法和 set_terminate() 是同一个系列的。
Since exception specifications are logically part of a function’s declaration, they too must remain consistent across an inheritance hierarchy.
- A derived class must not add any other exception types to base’s specification list.
 - You can, however, specify fewer exceptions or none at all.
 - You can also specify 
ExtExceptionforExtif there isBaseExceptionforBase. (可以理解为协变类型) 
9. Exception Safety
书上举了个例子:Standard C++ Library 的 stack,pop() 是个 void 函数。To retrieve the top value, call top() before you call pop(). 为什么要这么设计?直接把 pop() 设计为 int 不好吗?比如:
template<class T> 
T stack<T>::pop() {
	if(top == 0)
		throw logic_error("stack underflow");
	else
		return data[--top]; // If an exception was thrown here...
}
What happens if the copy constructor that is called for the return value in the last line throws an exception when the value is returned? The popped element is not returned because of the exception, and yet top has already been decremented, so the top element you wanted is lost forever!
The problem is that this function attempts to do two things at once: (1) return a value, and (2) change the state of the stack. It is better to separate these two actions into two separate member functions, which is exactly what the standard stack class does. (In other words, follow the design practice of cohesion, i.e. every function should do one thing well.) Exception-safe code leaves objects in a consistent state and does not leak resources.
另外还有一个概念叫 exception neutral,指不吞 exception。一个设计良好的 lib 应该同时具备 exception safe 和 exception neutral 这两个特性。
留下评论