Smart Pointers
Consider the program... When the scope of foo is entered storage for pointer x is created The new allocates storage on the heap class X {... When the scope foo is left, the storage for x is freed, but not for the X object pointed to by x An explicit delete must be called on the X object. static void foo( ) { X* x = new X( );...
A simple SmartPtr class //SmartPtrInitial.cc class X {... ; class SmartPtr { X* ; public: SmartPtr( X* p ) : ( p ) { ; X& operator*( ) { return *; X* operator->( ) { return ; (1) This class wraps a pointer to an X object (2) Overload of the deref operator ; ~SmartPtr( ) { delete ; cout << "Memory pointed to by freed up" << endl; (3) Overload member access operator (4) When the wrapped pointer is freed, the storage it int main() { X* xp = new X(); SmartPtr s( xp ); return 0; points-to is also freed (5) Create the object and get a pointer to it, and then wrap it in a smart pointer.
What the class does //SmartPtrInitial.cc class X { ; class SmartPtr { X* ; public: SmartPtr( X* p ) : ( p ) { ; X& operator*( ) { return *; X* operator->( ) { return ; ~SmartPtr( ) { delete ; cout << "Memory pointed to by freed up" << endl; ; int main() { X* xp = new X(); SmartPtr s( xp ); return 0; When the SmartPtr object s leaves the scope it was created in, it is freed and its destructor is called. When its destructor is called, it deletes the object pointed-to by the wrapped pointer (). This frees the storage in class X.
This simple class can also have problems //SmartPtrInitial.cc class X { ; class SmartPtr { X* ; public: SmartPtr( X* p ) : ( p ) { ; X& operator*( ) { return *; X* operator->( ) { return ; Consider the code: int main() { X* xp1 = new X( ); SmartPtr s1(xp1); SmartPtr s2 = s1; return 0; ; ~SmartPtr( ) { delete ; cout << "Memory pointed to by freed up" << endl; The default copy constructor will simply copy s1 into s2, and both will point to the same object. int main() { X* xp = new X(); SmartPtr s( xp ); return 0; When main exits, delete will be called on what is pointed to by xp1 (s1) and then by xp1 (s2). Delete is called twice on the same storage!
The last slide in pictures Consider the code: int main() { X* xp1 = new X( ); SmartPtr s1(xp1); SmartPtr s2 = s1; return 0; Immediately before exiting main xp1 s1 s2 After exiting The default copy constructor will simply main, xp1??? copy s1 into s2, and both will point to the same object. immediately before s2 s1 When main exits, delete will be called on released/ s2 what is pointed to by xp1 (s1) and then by deleted xp1 (s2). delete is called twice on the same storage!
Ownership The problem with the simple smart pointer class is that two different wrapped pointers think they own the object One way around this is to only allow one smart pointer to point to an object at a time Two useful helper functions are need p.release( ) makes the smart pointer p no longer point to its object and returns a pointer to the object q.reset(&obj) makes the smart pointer q delete its object and point to obj. When doing an assignment q = p first release p and then set q's pointer with a reset. q gets p's value and p points to null.
The release function X* release( ) { X* oldptr = ; // is class static = 0; release insures that a SmartPointer no longer has a pointer to an object. return oldptr; Before p O1 p p.release( ); After q Release release returns pointer to O1 O1 O2
void reset (X* newptr) { if (!= newptr) { reset delete ; = newptr; reset first frees the storage pointer to by a SmartPointer's current, and then sets the to a new value. before reset q O2 lhs.reset(&o1) After reset q O2 O1
Combining these into a copy constructor and assignment operator for smart pointers other points to null, points to // Copy constructor what other used to point to. SmartPtr(SmartPtr& other) : (other.release( )) { Note that other is not and cannot be a const parameter // assignment operator SmartPtr& operator=(smartptr& other) { if (this!= &other) lhs = other; // lhs, other SmartPointers reset(other.release( )); return *this; what lhs points to is deleted lhs points to what other pointed to
Assignment Actions other lhs Before O1 O2 // assignment operator SmartPtr& operator=(smartptr& other) { other lhs if (this!= &other) reset(other.release( )); After O2 return *this; Release release O1 returns pointer to O1 lhs = other; // lhs, other SmartPointers other lhs After O2 what lhs points to is deleted lhs points to what other pointed to reset O1
The new SmartPtr class class X {; class SmartPtr { X* ; public: explicit SmartPtr( X* p = 0 ) : ( p ) {; X& operator*() { return *; X* operator->() { return ; SmartPtr( SmartPtr& other ) : ( other.release() ) { // copy Every object pointed to by exactly one pointer Can safely delete what is pointed to by a smart pointer without fear of creating dangling references SmartPtr operator=( SmartPtr& other ) // assign, see previous slide ~SmartPtr() { delete ; X* release() // see previous slide void reset( X* newptr ) // see previous slide ; // end of SmartPtr class Doesn't cover the situation (like a doubly linked list) where you want an object pointed to by two or more pointers
Smart Pointers in C++ 11 see http://www.informit.com/articles/article.aspx?p=1944072 for a fairly detailed article C++ now has two standard smart pointers shared_ allows multiple pointers to the same object. Uses reference counting unique_ is similar to what we discussed weak_ allows a weak reference to a shared pointer object and aids in dealing with problems caused by reference counting. The circular dependence problem is present with shared_s.
shared_ usage examples // shared_ that points to an int with value 42 shared_<int> p3 = make_shared<int>(42); // p4 points to a string with value 9999999999 shared_<string> p4 = make_shared<string>(10, '9'); shared_<string> p1; // shared_ that can point to a string shared_<list<int>> p2; // shared_ that can point to a list of ints // if p1 is not null, check if it is the empty string if (p1 && p1->empty()) *p1 = "hi"; // if so, dereference p1 to assign a new value to that string
Don't mix shared and other pointers Shared pointers keep a reference count in the pointed-to object Only shared_ references are tracked If an object is pointed-to by a shared_ and another pointer, the object, and the last shared_ quits pointing to it, the object will be deleted the other pointers will be dangling references, and you are pretty much back in the same situation you would be in without shared pointers.
weak_ Point to a shared object A weak_ reference does not increment the count for the pointer, and therefore cannot prevent the object pointed-to from being deleted the lock function can be used to check if the pointer points-to an existing object. weak_ can be used to prevent circular references (e.g. in a doubly-linked list, use weak_ for back-pointers.
weak_ The lock function can be used to check if the pointer points-to an existing object. the lock function returns a shared_ to what is pointed to by the weak_ (if the weak_ is not expired, i.e., expired means there are no more shared_s pointing to that object.) Sp = wp->lock( ); The owning pointer is locked, preventing it from being
unique_ Similar to the smart pointers we discussed. Template based, more robust implementation than ours At most one pointer points-to an object at a time