/* Smart pointer (unique_ptr) from scratch using C++98 */ #include #include using namespace std; // A safe pointer (Safeptr) can be owned or not owned. A owned Safeptr tries to be // a unique pointer by overloading the copy constructor and copy assignment operators. // A non-owned Safeptr is considered a "borrow". It is not responsible for // deallocating memory. template class Safeptr { public: T* ptr; // "real pointer to value of type T" bool owned; // value is uniquely owned by pointer, responsible for its destruction Safeptr(T* x) { ptr =x; if (x!=NULL) owned=1; else owned=0; } // constructor Safeptr() { ptr=NULL; owned=0; } // creates "null ptr" - use nullptr in C++11 ~Safeptr() { if (owned && ptr!=NULL) delete(ptr); owned= 0; } // destructor // destructor does nothing to data if not owned. static Safeptr makesafe(T* x) { Safeptr sp(x); return sp; } bool is_owned() { return owned; } bool is_null() { return ptr==NULL; } Safeptr& borrowref() { return *this; } Safeptr borrow() // create a non-owned borrow (safe copy) { Safeptr br(ptr); br.owned = 0; return br; } T* giveup() { return ptr; } // give up trying to help the guy... T* move() // move pointer and ownership { owned=0; T* answer = ptr; ptr = NULL; return answer; }//move // C++ 98 does not allow r-value references, otherwise use Safeptr&& void swap(Safeptr& other) // swap ownership with other pointer { T* tmpptr = ptr; bool tmpowned = owned; ptr = other.ptr; owned = other.owned; other.ptr = tmpptr; other.owned = tmpowned; } // overloaded pointer operators T& operator *() { return *ptr; } //"deref coercion", (return l-value reference) T operator !() { return *ptr; } // returns "copy" of value T* operator ->() { return ptr; } //T&& borrow() { return *ptr; } // not available in c++ 98 void operator =(Safeptr& sp) // overload assignment operator { if (owned && ptr!=NULL) delete(ptr); bool wasowned = sp.owned; ptr = sp.move(); owned = wasowned; //throw "Safe Pointers can only be moved, not copied "; // note this is only a runtime error. wouldn't it be better to enforce // uniqueness of safe pointer at compile time? } Safeptr(/*const*/ Safeptr& sp) // copy constructor (same behavior as =) { if (owned && ptr!=NULL) delete(ptr); bool wasowned = sp.owned; ptr = sp.move(); // change to ptr= sp.ptr if const, but cons(2,..) won't compile owned = wasowned; }// copy constructor }; // Safeptr class ///// linked-list implementation using Safeptr template class cell { public: T car; Safeptr< cell > cdr; // no need for non-default destructor cell(T a, Safeptr< cell > &b) // never pass Safeptr by copy unless needed { car = a; cdr = b; // this calls copy assignment operator } };// cell class template Safeptr< cell > cons(T a, Safeptr< cell >& b) { Safeptr< cell > sp(new cell(a,b)); // heap allocation here return sp; } typedef Safeptr< cell > ilist; // how to print a list? void printlist0(ilist& M) // doesn't work: data gets MOVED! can't change aliases { ilist& current = M; // still doesn't work with ilist& while (!M.is_null()) { cout << current->car << " "; current = current->cdr; // overloaded = called. } cout << endl; }//printlist (what's wrong?) void printlist1(ilist& M) { cell* current = M.giveup(); // get actual pointer, voids unique_ptr guarantees while (current!=NULL) { cout << current->car << " "; current = current->cdr.giveup(); } cout << endl; } // why bother with unique_ptr in the first place? Can't do it without giving up? void printlist2(ilist& M) // doesn't work: data gets MOVED! can't change aliases { ilist& current = M; // doesn't work with ilist& current either while (!M.is_null()) { cout << current->car << " "; current = current->cdr; // overloaded = called. } cout << endl; }//printlist2 (what's wrong?) void printlist(ilist& M) // this works but g++ tail-call optimization fails { if (M.is_null()) {cout << endl; return; } cout << M->car << " "; printlist(M->cdr); } int main() { ilist Nil; //ilist N = cons(2,Nil); // won't compile because copy-constructor is not const ilist M(new cell(3,Nil)); ilist N(new cell(2,M)); ilist M2(new cell(6,Nil)); //how was nil reused? by alias to the same nil ilist N2(new cell(4,M2)); printlist(N); printlist(N); printlist(N2); // references can't be used efficiently except perhaps with /* vector V(3); V[0] = 1; V[1] = 2; V[2] = 3; for(int& x:V) cout << x << " : "; // c++11 only // x references each V[i] in turn. cout << endl; */ return 0; }//main // compiles and runs under g++ safepointer.cpp -std=c++98