Overloaded Assignment, Copy Constructor, Destructor in C++

8 minute read

Rule of Three

  • the Rule of Three is used for any object that dynamically allocates memory. Will need

    • Copy Constructor
    • Assignment Operator Overloaded (=)
    • Destructor
  • Rule: If you need one, then you need all three!!!!

I will go through an example as I expain this important concept(s).

Notes

"Insert Image" "Insert Image" "Insert Image" "Insert Image" "Insert Image"

Recall: Friend Function

Friend functions allows access to private data memebers, but again it’s not an actual memeber of the class.

Character Object

Let’s work through the example of a Character Class, that has an pointer array for the Tool/Weapon belt in which we will create multiple objects (Characters) and use Copy Constructor, Default Constructor, Overloaded Assignment, and Deconstructor.

Regular Constructor

GCharacter(std::string name = "John", int capacity = DEFAULT_CAPACITY );

Heres the regular/Default constructor implemented in the GCharacter.cpp file:

GCharacter::GCharacter(string n, int cap)
{   // Default Constructor where n = name and cap = capacity
    name = n;
    capacity = cap;
    used = 0;
    toolHolder = new string[cap]; // Allocates new Memory on the HEAP!!
}

Overloaded Assignment

Why do we need a Overloaded Assignment Operator?

  • To avoid creating a Shallow Copy, we can use a Overloaded Assignment Operator that performs a Deep Copy and allow multiple objects to have their own copies of the array in our example.

Heres the Syntax:

GCharacter& operator = (const GCharacter& source);

Heres the Overloaded Assignment Implementation in the GCharacter.cpp file:

GCharacter& GCharacter::operator=(const GCharacter& source)
{
    // Testing for self-assignment
    cout << "Overloaded Assignment Called. " << endl;
    
    // Check for self assignment
    // gc1 = gc1;
    if (this == &source)
    {
        return *this; // return GCharacter Object
    }
    this->name  = source.name;
    this->capacity = source.capacity;
    this->used = source.used;

    copy(source.toolHolder, source.toolHolder + used, this->toolHolder );

    return *this; // return GCharacter Object
}

In this case C2 already exists and so does C3 (we declared C3 an Object), now if we set C3 = C2 the Operator Overloaded Constructor will be called and create a new array pointing to new memory for the tool belt/array.

"Insert Image"

Copy Constructor

Why would we need a Copy Constructor?

  • Lets say for example we had object C2 (it already exists), and now were trying to create a new object called C3 and it doesn’t exist yet. But were going to initialize it with the values that currently exist in C2 (C3 = C2), thinking we can just change out (with setters) the attributes. But what happens is, even if we have an overloaded assignment operator, it’s not going to invoke in this case becuase C3 isn’t in existence. Instead, its going to invoke the default constructor and by default performs a shallow copy. It simply just gets the address that is being copied from C2 to C3. So in our example, it would end up sharing the exact same tool array on the Heap.
GCharacter(const GCharacter& source);

Heres the Copy Assignment Implementation in the GCharacter.cpp file:

GCharacter::GCharacter(const GCharacter& source)
{
    cout << "Copy Constructor Called. " << endl;

    this->name  = source.name;
    this->capacity = source.capacity;
    this->used = source.used;
    toolHolder = new string[source.capacity]; // In order to do a  Deep Copy, we need to create a brand new string array

    copy(source.toolHolder, source.toolHolder + used, this->toolHolder );
}

"Insert Image"

Deconstructor

Why do we need a Deconstructor?

  • Becuase in C++ when we create (allocate memory) on the Heap, we must take care of it by deleting it when were done using it. Memory Manegment is big in C++!!
 ~GCharacter(); 

Heres the Deconstructor implemented, it deletes the memory allocated by our Character Object for the Tool Belt allocated on Heap.

GCharacter::~GCharacter()
{
    // only handling the dynamic memory
    cout << "Destructor called for " << this->name << " at this memory location " << this << endl;

    delete[] toolHolder; 
}

Full Code Here

Character Header File (GCharacter.h)

#ifndef GCHARACTER_H_
#define GCHARACTER_H_

#include <iostream>
#include <string>

class GCharacter
{
    private:
    // Data Members (Attributes)
    std::string name;           // Name of Character
    int capacity;               // Capacity/Size of Tool Belt/Array
    int used;                    // Number of Spaces on Tool Belt/Arrat
    std::string* toolHolder;

    public:

        static const int DEFAULT_CAPACITY = 5;

        // Constructor "Normal"
        GCharacter(std::string name = "John", int capacity = DEFAULT_CAPACITY );

        // Copy Constructor
        GCharacter(const GCharacter& source);

        // Overloaded Assignment 
        GCharacter& operator = (const GCharacter& source);

        // Destructor (easy to write)
        ~GCharacter();

        // Insert (member function) a new tool into the tool array (holder)
        void insert(const std::string& toolName);
    

    // Overloading the << operator (syntax) Friend Function
    friend std::ostream& operator <<(std::ostream& os, const GCharacter& gc);

};

#endif

GCharacter File Functions (GCharacter.cpp)

#include<iostream>
using std::cout; using std::endl; 
#include<string>
using std::string;
#include<algorithm>
using std::copy;

#include "GCharacter.h"


// Constructor

GCharacter::GCharacter(string n, int cap)
{
    name = n;
    capacity = cap;
    used = 0;
    toolHolder = new string[cap];
}

// Copy Constructor

GCharacter::GCharacter(const GCharacter& source)
{
    cout << "Copy Constructor Called. " << endl;

    this->name  = source.name;
    this->capacity = source.capacity;
    used = source.used;
    toolHolder = new string[source.capacity]; // In order to do a  Deep Copy, we need to create a brand new string array

    copy(source.toolHolder, source.toolHolder + used, this->toolHolder );
}

// Overloaded Assignment Operator

GCharacter& GCharacter::operator=(const GCharacter& source)
{
    // Testing for self-assignment
    cout << "Overloaded Assignment Called. " << endl;
    
    // Check for self assignment
    // gc1 = gc1;
    if (this == &source)
    {
        return *this; // return GCharacter Object
    }
    this->name  = source.name;
    this->capacity = source.capacity;
    used = source.used;

    copy(source.toolHolder, source.toolHolder + used, this->toolHolder );

    return *this; // return GCharacter Object
}


// Destructor

GCharacter::~GCharacter()
{
    // only handling the dynamic memory
    cout << "Destructor called for " << this->name << " at this memory location " << this << endl;

    delete[] toolHolder; 
}

// Inserting a new tool into our toolHolder

void GCharacter::insert(const string& toolName)
{
    if (used == capacity) 
    {
        cout << "Tool Holder is full. Cannot add any additional tools " << endl;
    }
    else
    {
        toolHolder[used] = toolName;
        used++; //increment
    }

}

std::ostream& operator<<(std::ostream& os, const GCharacter& gc)
{
    os << "Game Character " << gc.name << " has the following tools: " << std::endl; 
    
    // iterate over our tool array
    for(int i = 0; i < gc.used; i++)
    {
        os << gc.toolHolder[i] + " | ";
    }
    return os << std::endl;
}

Main File to Run Program (main.cpp) showing exCopyConstructor…

Let’s create an example where we pass to a function a GCharacter Object and also return a GCharacter Object.

When we pass a GCharacter Object to a function, we will get an invoacation of the copy constructor and also whenever we return a GCharacter Object we’ll get an invocation of the copy constructor.

We named it exCopyConstructor

#include <iostream>
using std::cout;
using std::endl;

#include "GCharacter.h"

GCharacter exCopyConstructor(GCharacter tempGC)  
{   // Pass GCharacter by Value

    cout << "Copy Constructor called twice." << endl;
    cout << "Once for the Formal Parameter being passed by value" << endl;
    cout << "Once for the return value." << endl; 

    return tempGC;
}

int main()
{
    GCharacter gc1;

    gc1.insert("Potion");
    gc1.insert("Crossbow");
    gc1.insert("Gun");
    gc1.insert("Ray Gun");
    gc1.insert("Cloak");
    gc1.insert("Sword"); // won't get added in since we have a tool array of 5 by default

    cout << gc1 << endl;

    cout << "Creating another Character g2 Named Devin!!!! " << endl;

    GCharacter gc2("Devin", 5); // 5 size for the tool array

    gc2.insert("Axe");
    gc2.insert("Sword");
    gc2.insert("Knife");


    cout << gc2 << endl;

    // Call to exCop
    exCopyConstructor(gc2);

}

Output:

Tool Holder is full. Cannot add any additional tools 
Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 

Creating another Character g2 Named Devin!!!! 
Game Character Devin has the following tools: 
Axe | Sword | Knife | 

Copy Constructor Called. 
Copy Constructor called twice.
Once for the Formal Parameter being passed by value
Once for the return value.
Copy Constructor Called. 
Destructor called for Devin at this memory location 0x7ffee0afd480
Destructor called for Devin at this memory location 0x7ffee0afd458
Destructor called for Devin at this memory location 0x7ffee0afd508
Destructor called for John at this memory location 0x7ffee0afd5e8

When called the exCopyConstructor we invoke the main Copy Constructor in our GCharacter.cpp file. Then since we passed our Object by value to the exCopyConstructor, the function is invoked and prints (cout) out the 3 lines. Then the main Copy Constructor is called again once we return our GCharacter Object.

Now about the Destructors:

in this example copy constructor, we had two GCharacter objects being placed on the stack, one that’s being passed in and the one that is being returned so both of those get destroyed whenever we terminate the function. Those are the first two lines of the Destructor called.

The next two lines relate to gc1 and gc2 being popped off the stack when we terminate the main function.

Other Main File Showing Overloading Operator

Example Program being run to show Regular/Default Constructor, Copy Constructor, and Overloaded Operator being called and used.

#include <iostream>
using std::cout;
using std::endl;

#include "GCharacter.h"


int main()
{
    cout << endl;
    cout << "Creating Another Character gc1: " << endl;
    GCharacter gc1;      

    gc1.insert("Potion");
    gc1.insert("Crossbow");
    gc1.insert("Gun");
    gc1.insert("Ray Gun");
    gc1.insert("Cloak");
    gc1.insert("Sword"); // won't get added in since we have a tool array of 5 by default

    cout << "gc1: " << endl;
    cout << gc1 << endl;

    cout << "Creating Another Character gc2: " << endl;

    GCharacter gc2("Devin", 5);

    gc2.insert("Axe");  // insert into gc2
    gc2.insert("Potion");
    gc2.insert("Ray Gun");

    cout << "gc2: " << endl;
    cout << gc2 << endl;
    
    // Copy Constructor 
    cout << "Creating Another Character gc3: " << endl;

    GCharacter gc3 = gc2; // gc3 wasn't in existence

    cout << "gc3: " << endl;

    cout << gc3 << endl;


    // Over loaded Assignment Operator

    gc2 = gc1;

    // overloaded insertion operator
    cout << "_____________________ " << endl;
    cout << "gc2: " << gc2 << endl;
    cout << "gc1: " << gc1 << endl;
    cout << "gc3: " << gc3 << endl;

}

Output


Creating Another Character gc1: 
Main Constructor being Called 
Tool Holder is full. Cannot add any additional tools 
gc1: 
Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 

Creating Another Character gc2: 
Main Constructor being Called 
gc2: 
Game Character Devin has the following tools: 
Axe | Potion | Ray Gun | 

Creating Another Character gc3: 
Copy Constructor Called. 
gc3: 
Game Character Devin has the following tools: 
Axe | Potion | Ray Gun | 

gc1: Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 

Overloaded Assignment Called. 
_____________________ 
gc2: Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 

gc1: Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 

gc3: Game Character Devin has the following tools: 
Axe | Potion | Ray Gun | 

Destructor called for Devin at this memory location 0x7ffee9b77480
Destructor called for John at this memory location 0x7ffee9b77508
Destructor called for John at this memory location 0x7ffee9b775e8

"Insert Image" "Insert Image" "Insert Image"

Updated: