// 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. // compile with g++, but sometimes the -std=c++11 option is needed #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; // replace L->cdr with another list. L->cdr = cons(L->car+1,cons(10,cons(20,NULL))); // memory leak // the original L->cdr is leaked // BUT calling delete(i) here may not be valid either if shared. } //////////// // 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 void print() { /* print cannot be written this way (only prints once!) unique_ptr uthis(this); // uthis will be deleted before return unique_ptr& current = uthis; while (current!=nullptr) { cout << current->car << " "; // -> overloaded on unique_ptr current = move(current->cdr); // won't be able to print again! } cout << endl; */ // print can be written this way, but not without risk: ucell* current = this; while (current!=nullptr) { cout << current->car << " "; current = current->cdr.get(); // force unique_ptr to "give it up" // but now I have a real pointer, and EARTH IS DOOMED! //current = new ucell(400,nullptr); //current = new ucell(500,nullptr); // memory leak! } cout << endl; }//print }; // class ucell typedef unique_ptr upcell; // for convenience // do not mix unique_ptr with regular pointers, else lose guarantees // This is a safer way to write function to iterate over a list: // use recursion, so each rec. call a new reference var is created. 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); } else cout << endl; }// however, this function is not really tail-recursive, because the // referenced value of uc need to persist in the current stack frame. int uclength(unique_ptr const &uc, int ax=0) // length of list { if (uc==nullptr) return ax; else return uclength(uc->cdr, ax+1); } // But tail-recursion with call-by-reference is often not possible. 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,nullptr)))); ucprint(M); ucprint(M); // printing does not destroy list cout << "M has length " << uclength(M) << endl; //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 prints nothing cout<< "leaking? ...\n"; //M2=move(noleakref(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. Clearly, only a highly advanced programmer can take advantage of unique_ptr properly. There is no guarantee that the programmer won't mix unique_ptr with regular, unmanaged pointers (see the print method). Mixing them will re-create the same problems that unique_ptr was designed to solve, and may make it worse. Because using unique_ptr can be very contraining to the program, one can call .get() and .release() on a unique_ptr to get a real pointer, with which all sorts of havoc can ensue. 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 after a move, or dereferenced, or moved again, at compile time. So memory safety only improves at runtime. crash). Grade for this new feature of C++ is a C+. It does offer some protection. */