/* Smart Pointers in C++, aka Motivations for Rust Smart pointers in C++ go back decades. The idea is to use operator overloading to create an object with pointer like behavior, but which will managed code underneath. An object created on the stack in C++ will have its destructor automatically called when it goes out of scope. This is memory management technique in C++. How is this different from smart pointers such as Box and Rc in Rust? 1. There is nothing in C++ that says you HAVE TO use them. Of course it's possible to write good programs in any language if you're always disciplined and don't make mistakes. Always, 100%, for every programmer. Google "linked list class in C++" and see what you get. Pray that these people never write programs for you. 2. Even if you use smart pointers, the rest of the language is so unrestricted that it is very easy to break them. Furthermore, no matter how sophisticated the smart pointer implementation, the controls over memory safety can only be applied at runtime. At compile time, let's just say that C++ is rather open minded about a lot of things... In order to share linked lists in Rust, you MUST use the Rc smartpointer (or create your own). However, you can only share immutable data. To share mutable data: such as an immutable (non-destructible) list that contains mutable pointers (see the 'environ' type used by the F# eval program), you will have to also use a 'RefCell' smartpointer. But there is a significant cost: you loose the compile-time memory safety checks: violations are only reported at runtime as "panics". Thus, Rust also has its limits. There is actually a part of Rust that allows you to write "unsafe" code, the kind of code you can write in C++. Why, after all that fuss? Clearly because there are still things that you might want to do that do not fit the restrictions of Rust. This is not the fault of Rust's designers, but rather due to fundamental limitations of what can be done at compile time versus runtime (i.e. it goes back to the undecidability of the Halting Problem). A language with a garbage collector will never have any of these problems. But what if you want to WRITE a garbage collector? A garbage collector should not require a garbage collector to run! This is where you have to use a language that's more suited for systems programming. But benefiting from strong static checks also implies compromises in terms how you can write programs. */ #include using namespace std; bool trace = 0; template class Smptr // my own generic smart pointer class { protected: T *p; // encapsulated pointer bool owned; // avoids duplicate delete after data MOVED - Rust termo public: Smptr(T* p0) { p = p0; owned=1; } // constructor ~Smptr() { if (owned && p!=NULL) { /*owned=0;*/ delete(p); } } // overload operators: T& operator *() { return *p; } //most sp impls contain this, not safe T operator +() { return *p; } T& operator -() { if (p->cdr!=NULL) delete(p->cdr); return *p; }//testing T* operator &() { return p; } T* operator ->() { return p; } void operator =(Smptr &sp) { delete(p); // delete local before changing it p = sp.p; // copy over pointer ... sp.owned=0; // and TRANSFER OWNERSHIP to this Smptr - Rust termo again. owned=1; // however, this control is only implemented at runtime } }; //Smptr class cons // basic linked list class for experiment { public: int car; cons *cdr; cons(int a, cons* b) {car=a; cdr=b;} ~cons() { if (trace) { cout << "deallocating list with car " << car << " cdr " << cdr << endl; cout.flush(); } if (cdr!=NULL) delete(cdr); // not efficient but ok for this experiment }// destructor void print() { for (cons* i=this;i!=NULL;i=i->cdr) cout<< i->car << " "; cout << endl; } };//cons class for linked lists /////////// Key test function /////// void DoILeak() { Smptr list1(new cons(2,new cons(3,new cons(5,new cons(7,NULL))))); //*list1=*(new cons(10,new cons(220,NULL))); // memory leak Smptr list2(new cons(1,new cons(10,new cons(100,NULL)))); //*list1 = *list2; // double delete, sementation fault or worse list1 = list2; // no memory leak only if using overloaded = } int main(int argc, char** argv) { if (argc>1) trace = 1; Smptr list(new cons(2,new cons(3,new cons(5,NULL)))); cons *b = list->cdr->cdr; *list = *(new cons(10, new cons(20,b))); // what happened to 2 and 3? b->print(); // b list was not dealloc'ed list->print(); // no problem Smptrlist2(list->cdr); list2->print(); // still ok, but list, list2 will cause double delete // when main ends (expect core dump at end). for(int i=0;i<20000000;i++) { DoILeak(); if (trace) {cout << "---endfun---\n"; cout.flush();} } cout << "Did it leak?\n"; return 0; }// main /* My conclusion from this experiment is that, while smart pointers look like a good idea, ultimately it does not provide a practical solution to memory management. It is too fragile and too difficult to use correctly, and when mixed with "normal" pointers, creates a situation that's even more difficult to manage than not using them at all. Furthermore, what protections it does offer are mostly at runtime, not compile time. All this calls for some changes to the C++ language itself, which happened in 2011... */