Exception Safe Smart Pointers. Revised. Again. ANSI X3J16/94-0202R1, ISO WG21/N0589R1 Gregory Colvin Information Management Research gregor@netcom.com In C++ it is difficult to ensure that objects with dynamic storage duration are destroyed in a timely fashion. In the face of exceptions this perennial problem becomes even more difficult. This proposal specifies two smart pointer templates to ease the difficulty. This proposal differs from 94-0202/N0589 by the addition of a copy constructor and assignment operator to the auto_ptr template, a cleanup of the specifications to better represent the working group consensus, and some new discussion which addresses briefly some of the issues which were raised in full committee at Valley Forge. The template auto_ptr --------------------- The auto_ptr template provides a semantics of strict ownership. An auto_ptr owns the object it holds a pointer to, and deletes that object when it itself is destroyed. An object may be safely pointed to by only one auto_ptr, so copying an auto_ptr copies the pointer and transfers ownership to the destination. Interface template class auto_ptr { X* px; // exposition only public: explicit auto_ptr(X* p=0); template auto_ptr(const auto_ptr& r); ~auto_ptr(); template auto_ptr& operator=(const auto_ptr& r); X& operator*() const; X* operator->() const; X* get() const; X* release(); void reset(X* p=0); }; Semantics An auto_ptr object holds a pointer to an object of class X, presented here as X* px. In the following table: p and px are pointers to an object of class X or a class derived from X for which delete(X*) is defined and accessible, or else null; d is an auto_ptr where D is X or a class derived from X; a is an auto_ptr; and m is a member of X. Expression Value Effect ---------------- -------------- --------------------- auto_ptra(p) a.px = p auto_ptra(d) a.px = d.release() a.~auto_ptr() delete a.px a = d reference to a a.reset(d.release()) *a *a.px a->m a.px->m a.get() a.px a.release() a.px a.px = 0 a.reset(p) delete a.px, a.px = p The template counted_ptr ------------------------ The counted_ptr template provides a semantics of joint ownership. Each counted_ptr has an interest in the object it holds a pointer to, which it gives up when it itself is destroyed. An object may be safely pointed to by more than one counted_ptr, so long as the object is not deleted while any owner retains an interest. Interface template class counted_ptr { X* px; // exposition only public: explicit counted_ptr(X* p=0); template counted_ptr(const counted_ptr& r); ~counted_ptr(); template counted_ptr& operator=(const counted_ptr& r); X& operator*() const; X* operator->() const; X* get() const; bool unique() const; template counted_ptr dyn_cast() const; }; Semantics A counted_ptr object holds a pointer to an object of class X, presented here as X* px. In the following table: p and px are pointers to an object of class X or a class derived from X for which delete(X*) is defined and accessible; d is a counted_ptr where D is X or a class derived from X; c is a counted_ptr; m is a member of X; and u is an counted_ptr. Expression Value Effect ------------------- -------------- --------------------------- counted_ptrc(p) c.px = (X*)p counted_ptrc(d) c.px = (X*)d.px c.~counted_ptr() if (c.unique()) delete c.px c = d reference to c c.px = (X*)d.px *c *c.px c->m c.px->m c.get() c.px c.unique() true if and only if there exists no other u which is a copy of c such that c.px == u.dyn_cast().px u.dyn_cast() counted_ptr(dynamic_cast(u.px)) Discussion Bjarne Stroustrup asked at Valley Forge: Why two smart pointers? and Why counted_ptr? The answer to the first question is "efficiency". The semantics of auto_ptr are nearly a subset of counted_ptr, so counted_ptr alone might suffice, but auto_ptr can be implemented with less overhead than counted_ptr. As to the need for counted_ptr, let me quote Bjarne's "The C++ Programming Language, Second Edition", page 465: One of the most critical issues of the design of libraries and long running programs is memory management. ... The fundamental question about memory management can be stated in this way: If f() passes or returns a pointer to an object to g(), who is responsible for the objects destruction? A secondary question must also be answered: When can it be destroyed? ... From the point of view of a library provider, the ideal answers are "the system" and "whenever nobody is using the object any longer." The counted_ptr template comes close to this ideal, answering Bjarne's questions with "the counted_ptr destructor" and "when no other counted_ptr holds a pointer to the object." Bjarne goes on in the same chapter to recommend and implement a Handle template with the same semantics as counted_ptr, other authors of C++ texts and tutorials describe similar templates, and many programmers and libraries implement reference counted classes and class templates, so counted_ptr can be fairly described as standardizing a well-understood and wide-spread existing practice. The auto_ptr template is less established as existing practice, although Bill Gibbons reports favorable experience with a similar template at Taligent. However, I designed auto_ptr not to codify an existing practice, but to serve as a simple substitute for an existing practice which is unsafe in the face of exceptions. Thus the unsafe code void f() { T* p = new T; p->g(); delete p; // not called if g throws } can now be replaced with void f() { auto_ptr a = new T; a->g(); } Uwe Steinm|ller asked why I propose no auto_ptr for arrays. My presumption is that void f(int n) { T* p = new T[n]; g(p,p+n); delete p; // not called if g throws } can already be replaced with void f(int n) { vector v(n); g(v.begin(),v.end()); } so no new facility is needed. If anyone presents common idioms in which arrays cannot be safely replaced with vector objects we will need to revisit this question. The differences between auto_ptr and counted_ptr make a difference in the kinds of data structures one can construct with them. The auto_ptr template provides exception safe idioms only for objects whose addresses are kept in automatically allocated objects, or in dynamically allocated objects whose auto_ptr dependencies form a directed list or tree. The counted_ptr class can also handle dynamically allocated objects whose counted_ptr dependencies form a directed acyclic graph. The directed acyclic graph is of course a very common form of data structure. Bjarne argues (in "The Design and Evolution of C++", page 200) that the purpose of a standard library is to provide "building blocks" that "ease communication between separately-developed, more ambitious libraries." Since pointers to objects are a fundamental medium of communication in C++, I believe that the auto_ptr and counted_ptr templates are just the kind of building block that should be provided by the Standard Library.