How to implement reference counting when creating your own C ++ class?

That is, you need a counter that would store the number of links that reference the object. If this counter is zero, then the object must be deleted. I want to understand this, but I have not the slightest idea how to implement it.

    4 answers 4

    The simplest option is something like this (this is a sketch, nothing more):

    class CountedPtr { ... Type1 p1; // Данные Type2 p2; // Данные .... int * cnt; // Счетчик 

    In the constructor - among other things -

     cnt = new int(1); 

    In the copy constructor, cnt copied and incremented by 1. Accordingly, the assignment is processed (the easiest way to implement it is through the idiom of the exchange with the copy). In the destructor - something like

     if (--*cnt == 0) { delete ptr; delete cnt; } 

    To start (and understand the basics) enough?

    • 2
      I would like to see the answer of the one who minus - you probably have a much more correct idea of ​​what is happening, so share! - Harry
    • such people sit in the shade usually, I will raise - Stanislav Petrov
    • I've also put a minus. First of all, because this answer does not answer the question - the counter will be separate from the class instance. There is probably the simplest version of shared_ptr - VTT
    • Yes, I was about it and imagined it, but there was a problem. Suppose we all implemented this, and there is the following code: CounterPtr ptr1 = *something*; CounterPtr ptr2 = ptr1; CounterPtr ptr1 = *something*; CounterPtr ptr2 = ptr1; After deleting the ptr2 object, it is necessary that the reference count of the ptr1 object is ptr1 by one. How to realize this moment? Apparently, the idiom of sharing with a copy, just about that? Thank. - Ivan
    • @ Ivan So do you still need an external counter or counter for your class? - VTT

    Everything is already implemented: use std::shared_ptr .

    • Any examples? I understand how to use shared_ptr in the function main , by type: shared_ptr<int> a (new int(10)); shared_ptr<int> b (new int(0)); b = a; shared_ptr<int> a (new int(10)); shared_ptr<int> b (new int(0)); b = a; But I do not understand how to embed it in a class. And is it possible to use it with arrays, for example char[] ? Should the count be implemented in the constructor? Or, in general, poorly understood. - Ivan
    • What is the problem of embedding in a class? - Pavel Mayorov

    Here Stroustrup has reference counting when implementing the String class.

     // String.h #include <cstring> #include <iostream> #define DEBUG #ifndef DEFINE_STRING #define DEFINE_STRING class String { struct Srep { char* s; int n; int sz; Srep(int, const char*); ~Srep() { delete[] s; } Srep* GetOwnCopy(); void Assign(int, const char*); private: Srep(const Srep&); Srep& operator=(const Srep&); }; class Cref { public: inline Cref(String&, int); inline Cref(const Cref&); Cref(); inline operator char() const; inline void operator=(char); private: String& s; int i; }; public: class Range { }; inline String(); inline String(const char*); String(const String&); ~String(); String& operator=(const char*); String& operator=(const String&); inline void Check(int) const; inline char Read(int) const; inlinevoid Write(int, char); inline Cref operator[](int); inline char operator[](int) const; inline int Size() const; private: Srep* rep; }; inline String::String() : rep(new Srep(0, "") ) { #ifdef DEBUG std::cout << "inline String::String()" << std::endl; #endif } inline String::String(const char* p) : rep(new Srep(strlen(p), p) ) { #ifdef DEBUG std::cout << "inline String::String(const char* p)" << std::endl; #endif } inline void String::Check(int i) const { #ifdef DEBUG std::cout << "inline void String::Check(int i) const" << std::endl; #endif if (i < 0 || rep->sz <= i) throw Range(); } inline char String::Read(int i) const { #ifdef DEBUG std::cout << "inline char String::Read(int i) const" << std::endl; #endif return rep->s[i]; } inline void String::Write(int i, char c) { #ifdef DEBUG std::cout << "inline void String::Write(int i, char c)" << std::endl; #endif rep = rep->GetOwnCopy(); rep->s[i] = c; } inline String::Cref String::operator[](int i) { #ifdef DEBUG std::cout << "inline String::Cref String::operator[](int i)" << std::endl; #endif Check(i); return Cref(*this, i); } inline char String::operator[](int i) const { #ifdef DEBUG std::cout << "inline char String::operator[](int i) const" << std::endl; #endif Check(i); return rep->s[i]; } inline int String::Size() const { #ifdef DEBUG std::cout << "inline int String::Size() const" << std::endl; #endif return rep->sz; } inline String::Cref::Cref(String& ss, int ii) : s(ss), i(ii) { #ifdef DEBUG std::cout << "inline String::Cref::Cref(String& ss, int ii)" << std::endl; #endif } inline String::Cref::Cref(const Cref& r) : s(rs), i(ri) { #ifdef DEBUG std::cout << "inline String::Cref::Cref(const Cref& r)" << std::endl; #endif } inline String::Cref::operator char() const { #ifdef DEBUG std::cout << "inline String::Cref::operator char() const" << std::endl; #endif s.Check(i); return s.Read(i); } inline void String::Cref::operator=(char c) { #ifdef DEBUG std::cout << "inline void String::Cref::operator=(char c)" << std::endl; #endif s.Write(i, c); } #endif // String.cpp #include "String.h" String::String(const String& r) { #ifdef DEBUG std::cout << "String::String(const String& r)" << std::endl; #endif r.rep->n++; rep = r.rep; } String::~String() { #ifdef DEBUG std::cout << "String::~String()" << std::endl; #endif if (--rep->n == 0) delete rep; } String& String::operator=(const char* p) { #ifdef DEBUG std::cout << "String& String::operator=(const char* p)" << std::endl; #endif if (rep->n == 1) rep->Assign(strlen(p), p); else { rep->n--; rep = new Srep(strlen(p), p); } return *this; } String& String::operator=(const String& r) { #ifdef DEBUG std::cout << "String& String::operator=(const String& r)" << std::endl; #endif r.rep->n++; if (--rep->n == 0) delete rep; rep = r.rep; return *this; } String::Srep::Srep(int nsz, const char* p) { #ifdef DEBUG std::cout << "String::Srep::Srep(int nsz, const char* p)" << std::endl; #endif n = 1; sz = nsz; s = new char[sz + 1]; strcpy(s, p); } String::Srep* String::Srep::GetOwnCopy() { #ifdef DEBUG std::cout << "String::Srep* String::Srep::GetOwnCopy()" << std::endl; #endif if (n == 1) return this; n--; return new Srep(sz, s); } void String::Srep::Assign(int nsz, const char* p) { #ifdef DEBUG std::cout << "void String::Srep::Assign(int nsz, const char* p)" << std::endl; #endif if (nsz != sz) { delete[] s; sz = nsz; s = new char[sz + 1]; } strcpy(s, p); } 

      I will also leave here an example of my smart pointer pseudo-realization, since the question is still more about how to implement it yourself, rather than use ready-made classes (although it is better to use standard classes). This is just a learning example, because Once I was also interested in how smart pointers are arranged .

       template <class T> class my_smart_ptr { private: int* count; T* obj; public: my_smart_ptr(T* v) : obj(v) , count(new int) { *count = 1; std::cout << "my_smart_ptr: increment counter" << std::endl; } ~my_smart_ptr() { --*count; std::cout << "my_smart_ptr: decrement counter" << std::endl; if (*count == 0) { delete obj; obj = nullptr; delete count; count = nullptr; std::cout << "my_smart_ptr deleted the object. The count equals 0" << std::endl; } } T* operator -> () { return obj; } my_smart_ptr(const my_smart_ptr& other) { count = other.count; obj = other.obj; ++*count; std::cout << "my_smart_ptr: increment counter" << std::endl; } my_smart_ptr operator = (const my_smart_ptr& other) { my_smart_ptr<T> t(other); return t; } }; class example { private: int _a; public: example(int a) : _a(a) { std::cout << "example: Call Constructor example" << std::endl; } ~example() { std::cout << "example: Call Destructor example: a=" << _a << std::endl; } void setA(int a) { _a = a; } }; my_smart_ptr<example> foo() { auto t = my_smart_ptr<example>(new example(10)); int a = 10 + 5; a += 5; return t; } void foo2(my_smart_ptr<example> ex) { ex->setA(5); } void foo3() { my_smart_ptr<example> a = foo(); foo2(a); } int main() { foo3(); } 

      This is an example implementation of smart pointers. The essence of their work is as follows.
      There is a certain reference count that is a pointer to an int. Since this is a pointer, when you copy a smart pointer, the counter is copied by reference (and not by value), so it is common to all objects.
      The class example is non-flow safe . Standard smart pointers are thread safe.

      • Immediately again, the implementation of shared_ptr , almost added piece of the answer from Harry. - VTT
      • operator = implemented incorrectly: it does not assign anything to anything - Pavel Mayorov
      • The old version of the my_smart_ptr <T> t = new my_smart_ptr <T> (other) method implementation; will cause the copy constructor and the assignment to occur, why do you think that the assignment is not happening? I agree that in the assignment operator it is better not to write this because we can formally go into recursion, but modern compilers will cause the copy constructor and I don’t see any problems with the code. The answer clearly changed the call to the copy constructor (but in fact there are no changes in the method's operation) - Alexcei Shmakov