Technical part of More Effective C + + - Reference counting

  • Reference counting is a technology that allows multiple equivalent objects to share the same real value. There are two motives for the development of this technology. The first is to simplify the bookkeeping around the heap object. It can eliminate the burden of "recording object ownership", because when an object uses the reference counting technology, it has its own. Once it is no longer used by anyone, it will automatically destroy itself. The second development motivation of reference counting is just to realize a common sense. If many objects have the same value, it is foolish to store that value multiple times. It is best to let all equivalent objects share a real value.
  • The following is a Reference counting base class that includes copy on write technology. If you are interested, it is recommended to read the articles in the original book. The author guides you to write this class step by step. The writing is very detailed. I won't repeat it here for space reasons.

    // RCObject.h
    // template class, used to generate smart pointers-to-T objects;T must inherit from RCObject 
    template<class T>
    class RCPtr {
    public:
      RCPtr(T* realPtr = 0);
      RCPtr(const RCPtr& rhs);
      ~RCPtr();
    
      RCPtr& operator=(const RCPtr& rhs);
    
      T* operator->() const;
      T& operator*() const;
    
    private:
      T *pointee;
      void init();
    };
    
    // base class for reference counted objects
    class RCObject {
    public:
      void addReference();
      void removeReference();
    
      void markUnshareable();
      bool isShareable() const;
    
      bool isShared() const;
    
    protected:
      RCObject();
      RCObject(const RCObject& rhs);
      RCObject& operator=(const RCObject& rhs);
      virtual ~RCObject() = 0;
      
    private:
      int refCount;
      bool shareable;
    };
    
    // RCObject.cpp
    RCObject::RCObject() : refCount(0), shareable(true) {
    
    }
    
    RCObject::RCObject(const RCObject& rhs) : refCount(0), shareable(true) {
    
    }
    
    RCObject& RCObject::operator=(const RCObject& rhs) {
      return *this;
    }
    
    RCObject::~RCObject () {
    
    }
    
    void RCObject::addReference() {
      ++refCount;
    }
    
    void RCObject::removeReference() {
      if (--refCount == 0) {
          delete this;
      }
    }
    
    void RCObject::markUnshareable() {
      shareable = false;
    }
    
    bool RCObject::isShareable() const {
      return shareable;
    }
    
    bool RCObject::isShared() const {
      return refCount > 1;
    }
    
    template<class T>
    void RCPtr<T>::init () {
      if (pointee == 0)
          return;
      if (pointee->isShareable() == false) {
          pointee = new T(*pointee);
      }
      pointee->addReference();
    }
    
    template<class T>
    RCPtr<T>::RCPtr(T* realPtr) : pointee(realPtr) {
      init();
    }
    
    template<class T>
    RCPtr<T>::RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) {
      init();
    }
    
    template<class T>
    RCPtr<T>::~RCPtr() {
      if (pointee) {
          pointee->removeReference();
      }
    }
    
    template<class T>
    RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs){
      if (pointee != rhs.pointee) {
          if (pointee) {
              pointee->removeReference();
          }
          pointee = rhs.pointee;
          init();
      }
      return *this;
    }
    
    template<class T>
    T* RCPtr<T>::operator->() const {
      return pointee;
    }
    
    template<class T>
    T& RCPtr<T>::operator*() const {
      return *pointee;
    }
  • Then, define a simple String class. The String contains a struct StringValue representing its real value. The StringValue inherits from the above RCObject to enable it to count references. The String code is as follows.

    // String.h
    // Application class, which is the contact level of application developers
    #include <string.h>
    #include "RCObject.h"
    class String {
    public:
      String(const char *initValue = "");
    
      const char& operator[] (int index) const;
      char& operator[] (int index);
    
    private:
      // The following struct is used to represent the real value of the string
      struct StringValue : public RCObject {
          char *data;
    
          StringValue(const char *initValue);
          StringValue(const StringValue& rhs);
          void init(const char *initValue);
          ~StringValue();
      };
      RCPtr<StringValue> value;
    };
    
    // String.cpp
    void String::StringValue::init(const char *initValue) {
      data = new char[strlen(initValue) + 1];
      strcpy(data, initValue);
    }
    
    String::StringValue::StringValue(const char *initValue) {
      init(initValue);
    }
    
    String::StringValue::StringValue(const StringValue& rhs) {
      init(rhs.data);
    }
    
    String::StringValue::~StringValue() {
      delete [] data;
    }
    
    String::String(const char *initValue) : value(new StringValue(initValue)) {
    
    }
    
    const char& String::operator[](int index) const {
      return value->data[index];
    }
    
    char& String::operator[](int index) {
      if (value->isShared()) {
          value = new StringValue(value->data);
      }
      value->markUnshareable();
      return value->data[index];
    }
  • Write another main and verify it briefly.

    #include <String.h>
    #include <iostream>
    int main()
    {
      String s1 = "hello";
      String s2 = s1;
    
      std::cout << "s1[3] = " << s1[3] << std::endl;
      std::cout << "s2[3] = " << s2[3] << std::endl;
    
      s2[3] = 'x';
    
      std::cout << "s1[3] = " << s1[3] << std::endl;
      std::cout << "s2[3] = " << s2[3] << std::endl;
    }
// output
s1[3] = l
s2[3] = l
s1[3] = l
s2[3] = x
  • Reference counting is an optimization technology. Its premise is that objects often share real values. If this assumption fails, reference counting will lose more memory and execute more code. From another point of view, if your object does have the tendency of "common real value", reference counting should save you time and space at the same time.
  • In short, the following is the most appropriate time to use reference counting to improve efficiency:

    • A relatively large number of objects share a relatively small number of real values (if necessary).
    • Object real values are expensive to generate or destroy, or they use a lot of memory (not necessary).

Keywords: C++

Added by nonlinear on Thu, 09 Dec 2021 06:21:53 +0200