// When unique_ptr is not enough: std::shared_ptr and std::make_shared, // and std::weak_ptr // std::shared_ptr uses a reference counter: unlike unique_ptr, there // can be multiple shared_ptr's pointing to the same data. The data // is deallocated when the reference counter decreases to zero. Each // share increases the counter. This way of memory mangagement is quite // common and is found in Swift and other languages. But it's not // capable of completely freeing the programmer from worrying about memory: // cyclic pointers will never decrease the count, requiring the programmer // to identify some pointers as "weak" (std:weak_ptr). But this can be // tricky, and in the worst case requires the construction of spanning trees. #include #include #include // requires -std=c++20 using namespace std; void shared_demo() { shared_ptr sx = make_shared(1); auto sy = make_shared(10); cout << sx.use_count() << " : should be 1\n"; sy = sx; // shared pointers can be "copied" cout << sx.use_count() << " : should now be 2, the 1 is shared\n"; auto sz = move(sx); // as well as "moved": sx is no longer a owner cout << sz.use_count() << " : still 2: no new share created\n"; cout << sx.use_count() << " : should be 0, share got moved\n"; //cout << *sx < other; }; void share_and_leak() { auto a = make_shared(); auto b = make_shared(); a->other = b; // copy constructor applied, so another share is created.. b->other = a; } struct fixed_mutual_share { int A[10]; weak_ptr other; }; void no_more_leak() { auto a = make_shared(); auto b = make_shared(); a->other = b; b->other = a; // pointers are weak, does not increase ref. counter //a->other->A[0] = 10; // can't use weak_ptr to access data directly a->other.lock()->A[0] = 10; // temporary upgrade to shared_ptr } ///// own version of shared pointer, Rc for "reference counter" template // header class Rc; template // header class RcManager; template RcManager makeRc(Args&&...); int ccx{0}; // counter for diagnostics template class Rc { protected: RcManager* manager{nullptr}; Rc() {} public: operator bool() { return manager && manager.ptr!=nullptr; } T& operator *() { return *(manager->ptr); } T* operator ->() { return manager->ptr; } template friend RcManager makeRc(Args&&...); bool is_manager() { return manager==this; } unsigned int use_count() { if (manager) return manager->counter; else return 0; } // Rule of FIVE (so we can have rule of ZERO) // destructor ~Rc() { if (manager) manager->dec_counter(); } // copy = const Rc& operator =(const Rc& other) { if (is_manager()) { cerr << "Rc manager cannot be reassigned\n"; return other; } if (manager) manager->dec_counter(); // prevent mem leaks manager = other.manager; if (manager) manager->inc_counter(); //cout << "copy = called\n"; return other; } // copy constructor Rc(const Rc& other) { manager = other.manager; if (manager) manager->counter += 1; cout << "copy constructor called " << ++ccx << " times\n"; } // move = void operator =(Rc&& other) { if (other.is_manager()) { cerr << "Rc manager cannot be moved - copying assigning\n"; if (manager) manager->dec_counter(); // prevent mem leaks manager = other.manager; manager->inc_counter(); return; } if (manager) manager->dec_counter(); // prevent mem leaks manager = other.manager; other.manager = nullptr; // disconnect from data //cout << "move = called\n"; } // move constructor Rc(Rc&& other) { if (other.is_manager()) { // copy instead cerr << "Rc manager cannot be moved - copying instead\n"; manager = other.manager; manager->counter += 1; return; } manager = other.manager; other.manager = nullptr; // disconnect from data cout << "move constructor called\n"; } }; // Rc template class RcManager : public Rc // an RcManager is also an Rc { T* ptr{}; unsigned int counter{1}; RcManager(T* p) { // private constructor called from friend makeRc ptr = p; Rc::manager = this; //must access protected vars this way } void inc_counter() { counter++; } void dec_counter() { if (counter>0) { counter--; if (counter == 0 && ptr) delete ptr; } } // dec_counter public: template friend RcManager makeRc(Args&&...); //template friend class Rc; }; // RcManager subclass template RcManager makeRc(Args&&... args) { return RcManager(new TY(std::forward(args)...)); // forward retains original l/r-value status of args }// make_managed void RcDemo() { RcManager rc = makeRc(3); // this calls move constructor! RcManager rrr = makeRc(33); Rc rd = rc; *rd = 4; cout << rc.use_count() << " : init use count\n"; cout << *rc << " : should be 4\n"; cout << *rd << " : should be 4\n"; cout << rc.use_count() << " : should be 2\n"; Rc re = move(rd); // genuine move construction cout << re.use_count() << " : should still be 2\n"; cout << *re << " : should be 4 again\n"; } int main() { //shared_demo(); RcDemo(); //while (1) share_and_leak(); //while (1) no_more_leak(); return 0; }