ANSI X3J16/94-0142, ISO WG21/N0529 Library Facilities for Exception Safe Programming Gregory Colvin Information Management Research gregor@netcom.com Resources and Exception Safety What is exception safety? Let us consider some simple examples of unsafe functions. 1. The following function will fail to deallocate memory if fun() throws an exception: void unsafe_mem_fun(size_t buf_size) { char* buf= new char[buf_size]; if (buf) { fun(buf); delete buf; } } 2. The following function will fail to close a file if fun() throws an exception: void unsafe_file_fun(const char* name) { FILE* fp= fopen(name,"r"); if (fp) { fun(fp); fclose(fp); } } 3. The following function fail to flush a stream if fun() throws an exception: ostream& ostream::operator<<(int i) { if (opfx()) { fun(i); osfx(); } }; 4. The following function will fail to unlock a record if fun() throws an exception: void unsafe_lock_fun(int file, long offset, long length) { if (lock(file,offset,length)) { fun(file,offset,length); unlock(file,offset,length); } } 5. The following function will fail to zero a pointer if the destructor for T throws an exception: struct t_pointer { T* pointer; }; unsafe_delete(t_pointer* p) { delete p->pointer; p->pointer = 0; } Each of above examples would be safe in the absence of exceptions, but in the presence of exceptions acquired resources are left unreleased. "Resource Acquisition is Initialization" Our Standard library already provides safe alternatives for examples 1 and 2 which use the "resource allocation is initialization" (Stroustrup 1991) strategy. Consider void safe_mem_fun(size_t buf_size) { dynarray buf(buf_size); fun(buf); } which will not fail to deallocate memory in the face of an exception, and also void safe_file_fun(const char* name) { ifstream fs(name); fun(fs); } which will not fail to close a file in the face of an exception. We should consider what other resources the Standard library should manage, if any, and especially whether all the resources it does manage are managed safely (c.f. example 3). For many resources not managed by the Standard library users can write simple classes that are more efficient and easier to use than the alternative of wrapping all resource use in try blocks. Can we provide templates to help automate the construction of such classes? I think not. The concept of a resource is not that well defined, and resources have too many different interfaces to allow for a reasonably small and simple set of templates. Should we break existing code? For writing new code the strategy of managing resources with classes is fine, but it cannot help existing code without a rewrite. Until recently there were no exceptions to throw, but our Working Paper now mandates that the default ::operator new(size_t) will throw an exception, rather than returning 0, when memory is unavailable. Only in a footnote do we mention the possibility of an extension for restoring the traditional behavior. Since not all existing code will or can be rewritten, I recommend that set_new_handler(0) cause the default ::operator new(size_t) to return 0 when memory is unavailable; that is, we should make the footnote normative. I realize that we have already considered this, and that such a mandate could make implementing the Standard Library more difficult, but I prefer to put the burden on vendors rather than break existing code. Managing Free Store: The Hold and Delete Strategy Free store is perhaps the resource used most often by C++ programs. Free store is difficult to manage safely, even in the absence of exceptions, and I still believe that some form of garbage collection would be the best way to ease the difficulty. As an alternative to garbage collection Steve Rumbsy (at the Waterloo meeting) recommended a class template (substantially like one proposed by John Skaller on the c++std=lib reflector) for simply holding onto a pointer for eventual deletion, with an interface rather like template class pointer_deleter { T* pointer; pointer_deleter(pointer_deleter&) {} void operator=(pointer_deleter&) {} public: pointer_deleter(T* p) : pointer(p) {} ~pointer_deleter() { delete pointer; } void release() { pointer = 0; } }; Note that the constructor argument must be allocated by operator new, and that copying is disallowed. One simple usage of pointer_deleter is void safe_fun() { T* p = new T; pointer_deleter guard(p); fun(p); } which of course has much the same semantics as the even simpler function void safe_fun() { T t; fun(&t); } except for the practical matter of large objects on small stacks. A more interesting example is void safe_fun() { T* pointer = new T; pointer_deleter guard(p); if (fun(p)) guard.release(); } in which fun(p) may decide to take custody of p, which then need not be deleted. Of course fun(p) must not delete p before or while an exception is thrown, as guard would then delete p a second time. The pointer_deleter template is rather inelegant to use, limited to local management of memory, and easy for users to implement and fine- tune for themselves. I do not recommend that we add it to our Library. Managing Free Store: The Smart Pointer Strategy Some of the inelegance of the pointer_deleter template can be relieved by providing a smart pointer interface more like template class auto_pointer { T* pointer; auto_pointer(auto_pointer&) {} void operator=(auto_pointer&) {} public: auto_pointer(T* p) : pointer(p) {} ~auto_pointer() { delete pointer; } void release() { pointer = 0; } operator T*() { return pointer; } T* operator->() { return pointer; } }; which allows for usage like void safe_fun() { auto_pointer p = new T; if (fun(p)) p.release(); } Note that fun(p) must still be careful to not delete p before or while an exception is thrown. It might be better to have fun() do the releasing, but the hidden copy constructor prevents passing an auto_pointer to or returning an auto_pointer from a function. Thus auto_pointer remains generally useful only for local management of memory. The copy constructor and assignment operator for auto_pointer must be hidden to prevent the destructor from deallocating memory more than once. If assignment and copying were supported with a reference counting semantics (Colvin 1994, Stroustrup 1991) then a function taking a counted_pointer could simply make a copy of its argument without needing to inform its caller, allowing for usage like void safe_fun() { counted_pointer p = new T; fun(p); } Of course users and library vendors can (and do) implement reference counting for themselves, but it is surprisingly easy to get wrong, and having it in the Standard would make communication between libraries that much easier. I recommend that we consider adding a reference counted smart pointer to our Library. References 1. Colvin, G.A. Finalization Semantics for C++ Garbage Collection. ANSI X3J16/94-0141, ISO WG21/N0528 2. Stroustrup, B.S. The C++ Programming Language, Second Edition. Addison-Wesley, 1991.