// C++ (11+) std::unique_ptr // This program demonstrates and explores the limitations of std::unique_ptr // as a device for the safe use of memory. #include #include using namespace std; // conventional linked list implementation with no destructor: class cell { public: int car; cell* cdr; cell(int a, cell*b) {car=a; cdr=b;} void print() { for(cell* i=this;i!=NULL;i=i->cdr) cout << i->car << " "; cout << endl; }//print }; // class cell cell* cons(int x, cell* b) { return new cell(x,b);} // for convenience // sample function with memory leak void leak(cell* L) { cell* i=L->cdr; /* while (i!=NULL) { cell* temp = i; i = i->cdr; delete(temp); } */ // replace L->cdr with another list. L->cdr = cons(L->car+1,cons(10,cons(20,NULL))); // the original L->cdr is leaked } // re-implementation with std::unique_ptr class ucell { public: int car; unique_ptr cdr; ucell(int a, unique_ptr b) { car=a; cdr=std::move(b);} // move transfers ownership of b to cdr // default destructor suffices because of unique pointer // can't put print() here because it can't accept "this" but a unique_ptr }; // class ucell typedef unique_ptr upcell; // for convenience // do not mix unique_ptr with regular pointers, else lose guarantees void ucprint(upcell const &uc) // can't claim ownership of ucell { // uc is a "borrow" if (uc) { cout << uc->car << " "; // -> uses "deref coercion" ucprint(uc->cdr); // tail-recursive call can be optimized in C++ } else cout << endl; } upcell ucons(int a, upcell b) // note "move" must be used { upcell upc(new ucell(a,move(b))); //note move return upc; //move(upc); returns copy, so OK }//ucons unique_ptr noleak(unique_ptr U) { upcell R = ucons(U->car+3,ucons(100,ucons(200,NULL))); U->cdr = move(R); //need: if want to pass ownership back to caller return R; } void noleakref(unique_ptr &U) // this is better, but ... { upcell R = ucons(U->car+3,ucons(100,ucons(200,NULL))); U->cdr = move(R); // because cdr is unique_ptr, mem is not leaked. } int main() { cell* L = cons(2,cons(3,cons(5,cons(7,NULL)))); L->print(); //cout<< "leaking? ...\n"; //for(int i=0;i<1000000000;i++) leak(L); // monitor mem usage with sys monitor // write unique_ptr instead of ucell* unique_ptr M = ucons(11,ucons(13,ucons(17,ucons(19,NULL)))); //unique_ptr M2 = M; // compiler error: nolonger unique! need move unique_ptr M2 = move(M); // OK, ownership transferred unique_ptr M3 = move(M); // OOPS! moving twice also compiles! //ucprint(M); // BUT THIS STILL COMPILES, and results in runtime seg fault, // This is a FAILURE for C++. cout<< "leaking? ...\n"; //M2=move(noleak(move(M2)));// compiles but crashes //noleakref(M3) // twice moved pointer is not usable! (crashes) noleakref(M2); ucprint(M2); // this passes M2 by reference so no move needed. for(int i=0;i<1000000000;i++) noleakref(M2); // monitor mem usage return 0; }//main /* Does std::unique_ptr (and shared/weak_ptr) completely solve C++'s memory mangagement problems? It only solves it partially, not completely. 1. there is no guarantee that the programmer won't mix unique_ptr with regular, unmanaged pointers. Mixing them will re-create the same problems that unique_ptr was designed to solve, and may make it worse. 2. Although some errors like the lack of move is detected at compile time, the enhanced C++ 11 compiler still cannot detect all missuses of unique_ptr: in particular, it does not enforce that a unique_ptr can still be used, dereferenced, or moved again, at compile time. So memory safety only improves at runtime (better to crash than to leak memory which eventually leads to an unpredicted crash). Grade for this new feature of C++ is a C+. It does offer some protection. */