C++ shallow and deep copy and reference count analysis

  • 2020-05-19 05:28:29
  • OfStack

C++ shallow and deep copy and reference count analysis

In the development of C++, one of the most common problems is the memory management related to Pointers. If you make a mistake, it will cause serious problems such as memory leak and memory damage. Unlike Java1 sample, there is no pointer to this concept, so also don't have to worry about a series of issues related to Pointers, but different C + +, inherited from C language pointer is the one big characteristic, we often need to use new/delete to dynamic memory management, so the question comes, especially with the inheritance mechanism of C + +, such as wild Pointers, invalid pointer, memory leaks, double free, heap fragmentation, etc., these problems like mine 1 sample, one not careful will trample so few.

Let's talk first about the common shallow copy problem in C++ classes and the resulting double free. What is shallow copy? When members of the class variables include Pointers, and not to define your own copy constructor, so in the case of copying an object, it invokes the default copy constructor, this function is actually didn't do anything else, just for the member variable with a simple copy, also known as a copy, they point to is with a storage space, when the object destructor will destructor times, namely double free, illustrated below.


class Common
{
public:
  Common()
  {
    std::cout << "Common::Common" << std::endl;
  }

  Common(const Common &r)
  {
    std::cout << "Common::Common copy-constructor" << std::endl;
  }

  ~Common()
  {
    std::cout << "Common::~Common" << std::endl;
  }
};

The class Common is a 1-like class that defines constructs, copy constructs, and destructors, and outputs one log in the function to keep track of function calls.


class BitCopy
{
public:
  BitCopy()
    : m_p(new Common)
  {
    std::cout << "BitCopy::BitCopy" << std::endl;
  }

  ~BitCopy()
  {
    std::cout << "BitCopy::~BitCopy" << std::endl;
    if (m_p) {
      delete m_p;
      m_p = NULL;
    }
  }

private:
  Common *m_p;
};

Class BitCopy is a shallow copy class, the member variable is the class pointer we just defined, the constructor instantiates the member variable, the destructor delete member variable, no copy constructor is defined.


int main()
{
  BitCopy a;
  BitCopy b(a);
  return 0;
}
log As follows: 
Common::Common
BitCopy::BitCopy
BitCopy::~BitCopy
Common::~Common
BitCopy::~BitCopy
Common::~Common
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001f4e010 ***
 Have to give up  ( Core dump )

As you can see from log above, the object a calls the constructor, the object b calls the default copy constructor, and it is destructed twice, resulting in double free, which is core dump, the core has been dumped.

How to solve the above problems? There are two ways, one is deep copy and one is reference counting. Start with 1 under deep copy, deep copy to define your own copy constructor, to the member variables in the function to allocate storage space, that is, the value of the so-called copy, so that they point to is different storage space, destructor when there will be no problem, but this method is only applicable to small data structure, if the data structure is too big, after the allocation of storage for many times, the rest of the storage space will reduce gradually,

Here's an example.


class ValueCopy
{
public:
  ValueCopy()
    : m_p(new Common)
  {
    std::cout << "ValueCopy::ValueCopy" << std::endl;
  }

  ValueCopy(const ValueCopy &r)
    : m_p(new Common(*r.m_p))
  {
    std::cout << "ValueCopy::ValueCopy copy-constructor" << std::endl;
  }

  ~ValueCopy()
  {
    std::cout << "ValueCopy::~ValueCopy" << std::endl;
    if (m_p) {
      delete m_p;
      m_p = NULL;
    }
  }

private:
  Common *m_p;
};

Class ValueCopy is a deep-copy class. Unlike the shallow-copy class in the above example, a copy constructor is defined to redistribute storage space to member variables.


int main()
{
  ValueCopy c;
  ValueCopy d(c);
  return 0;
}
Common::Common
ValueCopy::ValueCopy
Common::Common copy-constructor
ValueCopy::ValueCopy copy-constructor
ValueCopy::~ValueCopy
Common::~Common
ValueCopy::~ValueCopy
Common::~Common

As you can see from log above, the object c calls the constructor, and the object d calls the custom copy constructor, which ends up being destructed twice without any problems, so you can see the use of deep copy.

Unlike deep copy, reference counting shares the same block of storage space, which is advantageous for large data structures. To use reference counting, we need to define a member variable in the class to be used for counting, the initial value is 1, then we will add 1 to this object when it is referred to, and subtract 1 from the reference when the object is destroyed, but it is not the real delete object, delete is only performed when the value of this member variable is 0, as shown in the following example.


class A
{
public:
  A()
    : m_refCount(1)
  {
    std::cout << "A::A" << std::endl;
  }

  A(const A &r)
    : m_refCount(1)
  {
    std::cout << "A::A copy-constructor" << std::endl;
  }

  ~A()
  {
    std::cout << "A::~A" << std::endl;
  }

  void attach()
  {
    std::cout << "A::attach" << std::endl;
    ++m_refCount;
  }

  void detach()
  {
    if (m_refCount != 0) {
      std::cout << "A::detach " << m_refCount << std::endl;
      if (--m_refCount == 0) {
        delete this;
      }
    }
  }

private:
  int m_refCount;
};

class B
{
public:
  B()
    : m_pA(new A)
  {
    std::cout << "B::B" << std::endl;
  }

  B(const B &r)
    : m_pA(r.m_pA)
  {
    std::cout << "B::B copy-constructor" << std::endl;
    m_pA->attach();
  }

  ~B()
  {
    std::cout << "B::~B" << std::endl;
    m_pA->detach();
  }

private:
  A* m_pA;
};

Class A USES reference counting, both the build and copy constructors are initialized to 1, attach() is a reference plus 1, detach() is a reference minus 1, and delete objects are used when the reference count is 0. The member variable in class B has a pointer to A, attach() is called in the copy constructor, detach() is called in the destructor, which is also a protection against memory leaks, and double free is not log, log is as follows.


int main()
{
  B e;
  B f(e);
  return 0;
}
A::A
B::B
B::B copy-constructor
A::attach
B::~B
A::detach 2
B::~B
A::detach 1
A::~A

As can be seen from log, the reference count of pointer member variable is 2, which is correct, and delete is correct in the end, no problem.

Note this as long as there are pointer member variables in the class, and the assignment operator operator= is overloaded when appropriate. Sometimes, if you want to get around the problem above, you can declare the copy constructor and the operator= operator to be private without implementing them.

Thank you for reading, I hope to help you, thank you for your support of this site!


Related articles: