📖
Go C++
  • Introduction
  • Chapter 1: What You Must Know First
    • Virtual Address Space of Process: Memory Partition and Layout
    • Function Call: Stack Frame
    • Program Compiling and Linking
  • Chapter 2: C++ Basics Improvement
    • Default Parameters
    • Inline Function
    • Function Overloading
    • new and delete
    • const and Pointers
    • References in Detail
  • Chapter 3: Object-Oriented Principles
  • Class and Object
  • Constructor and Destructor
  • Shallow Copy and Deep Copy
  • Initializer List
  • Various Member Functions
  • Pointer to Class Members
  • Chapter 4: Template Programming
  • Function Templates
  • Class Templates
  • Memory Allocators
  • Chapter 5: Operator Overloading
    • Operator Overloading
    • Introduction to Iterators
    • Issues of Iterator Invalidation
    • More about new and delete
    • Overloading of new and delete: Object Pool
  • Chapter 6: Inheritance and Polymorphism
    • Look inside Inheritance
    • More about Inheritance
    • Virtual Functions, Static Binding and Dynamic Binding
    • More about Virtual Functions
    • Understanding Polymorphism
    • Abstract Classes
    • Frequently Asked Interview Questions: Polymorphism
  • Chapter 7: Multiple Inheritance
    • Virtual Inheritance and Virtual Base Classes
    • Diamond Problem
    • Four Kinds of Type Conversions
  • Chapter 8: Standard Template Library
    • Sequence Containers
    • Container Adaptors
    • Associative Containers
    • More about Iterators
    • Function Objects
    • Generic Algorithms, Binders and Lambda Expressions
  • Chapter 9: Object Optimization
    • Behind the Object
    • Optimizing Objects in Functions
    • Member Functions with Rvalue References
    • Move Semantics and Perfect Forwarding
  • Chapter 10: Smart Pointers
    • Smart Pointers
    • Smart Pointers without Reference Counting
    • Smart Pointers with Reference Counting
    • Custom Deleters
  • Chapter 11: Function Objects and Binders
    • More about Binders
    • Introduction to std::function
    • Template Specialization and Argument Deduction
    • More about std::function
    • std::bind(): A Simple Thread Pool
    • More about Lambda Expressions
  • Chapter 12: Multithreading
    • Important Features in C++11
    • Multithreaded Programming with std::thread
    • Mutual Exclusion
    • Producer-Consumer Problem
    • Atomic Operations
    • Thread Visibility and volatile
  • Chapter 13: Design Patterns
    • Singleton Pattern
    • Factory Pattern
    • Proxy Pattern
    • Decorator Pattern
    • Adapter Pattern
    • Observer Pattern
Powered by GitBook
On this page

Was this helpful?

Shallow Copy and Deep Copy

PreviousConstructor and DestructorNextInitializer List

Last updated 4 years ago

Was this helpful?

Now let's continue with our MyStack class. Suppose we create a MyStack object s1, and use it to create two other stacks s2 and s3, with either operator = or brackets.

int main() {
    MyStack s1(10);
    MyStack s2 = s1;
    MyStack s3(s1);
    return 0;
}

By doing this, the compiler will create a copy constructor for us, which simply copy the memory space of s1 to s2 and s3. But the program crashes while running, the following image explains why.

Here in s1 we have a member variable *_pstack which points to an array we created on the heap. By copying the memory space of s1, s2 has an identical pointer *_pstack which points to the same array. When the function returns, it first calls s2's destructor, which destroys the array object. But when the destructor of s1 is called, the memory is already destroyed, so a illegal memory access happened.

The behavior of copying the memory space is called a shallow copy. In some cases a shallow copy is safe and sufficient, but when the object occupies external resources, the shallow copy will cause problems.

To fix this, we may use a deep copy instead. To achieve this, we can define the copy constructor by our own:

MyStack(const MyStack &other) {
    _pstack = new int[other._size];
    for (int i = 0; i < _size; i++) {
        _pstack[i] = other._pstack[i];
    }
    _top = other._top;
    _size = other._size;
}

The copy constructor has the same name as the class and no return value. It takes a reference to an object of this class as parameter. In the code above, we allocate new memory for the array, and manually copy all the element into it.

Now suppose we create two stacks, s1 and s2 with the constructor. And we want to override s2 with s1, with the operator =.

int main() {
    MyStack s1(10);
    MyStack s2(10);
    s2 = s1;
    return 0;
}

The default behavior of such assignment is also a shallow copy, which causes problem in releasing memory. And in addition, it will also cause a memory leak, because s2's array pointer loses its control of the array on the heap.

We can also define the operator = in the way we want, which is called an operator override.

MyStack& operator=(const MyStack &other) {
    if (this == &other)
        return *this;
    // 1. Release current memory
        delete[] _pstack;
    // 2. Allocate new memory
     _pstack = new int[other._size];
    for (int i = 0; i < _size; i++) {
        _pstack[i] = other._pstack[i];
    }
    _top = other._top;
    _size = other._size;
    
    return *this;
}

The function name of an operator override is the keyword operator plus the symbol. It also takes a reference to an object of this class as parameter. The return value can be void, but in most cases we return a reference to the current object *this. In this way we can achieve the relay of assignments, for example s1 = s2 = s3.

We need to do two things in the override of operator =. First, we need to release the memory of the current object. But what if we assign an object to itself? Then its memory is released and can not go on. In preventing this, we need to check if the object on the right side is the current object. Then we can do the deep copy as before.