-3

I created a test class to demonstrate the use of the overloaded assignment operator, and the pitfalls associated with double freeing memory. The code for this class is below:

class Test {
  public:
    Test() : ptr{new int(0)}, val{0}, id{count++} {}

    Test(int ptr_val, int new_val) : ptr{new int(ptr_val)}, val{new_val}, id{count++} {}

    ~Test()
    {
        delete ptr;
    }

    Test& operator=(const Test& rhs)
    {
        *ptr = *(rhs.ptr);
        val = rhs.val;
    }

    void setPtrVal(int ptr_val)
    {
        *ptr = ptr_val;
    }

    void setVal(int new_val)
    {
        val = new_val;
    }

    void printData() const
    {
        cout << "id  = " << id  << endl;
        cout << "val = " << val << endl;
        cout << "ptr = " << ptr << endl;
        cout << "*ptr = " << *ptr << endl << endl;
    }
  private:
    int* ptr;
    int val;
    int id;

    static int count;
};

int Test::count = 1;

And I am having two main functions which are testing this class with the overloaded assignment operator. I am putting the output for each main function directly below it's body.

Main 1:

int main()
{
    Test t1;
    Test t2(2, 2);

    t1.printData();
    t2.printData();

    t2 = t1;  // Overloaded Assignment Operator
    t1.printData();
    t2.printData();

    t2.setVal(3);
    t2.setPtrVal(3);

    t1.printData();
    t2.printData();

    return 0;
}

Output 1: (works as expected)

id  = 1
val = 0
ptr = 0x204dc20
*ptr = 0

id  = 2
val = 2
ptr = 0x204dc40
*ptr = 2

id  = 1
val = 0
ptr = 0x204dc20
*ptr = 0

id  = 2
val = 0
ptr = 0x204dc40
*ptr = 0

id  = 1
val = 0
ptr = 0x204dc20
*ptr = 0

id  = 2
val = 3
ptr = 0x204dc40
*ptr = 3

Main 2:

int main()
{
    Test t1(10, 15);

    {
        Test t2 = t1;
        t2.printData();
    }

    t1.printData();

    return 0;
}

Output 2 (does not work as expected):

id  = 1
val = 15
ptr = 0xd6fc20
*ptr = 10

id  = 1
val = 15
ptr = 0xd6fc20
*ptr = 0

*** Error in `./a.out': double free or corruption (fasttop): 0x0000000000d6fc20 ***

This is weird. For some reason, the pointer points to the same memory, and the id field is the same. It should be different. It appears that the default assignment operator is invoked. I don't know why this is the case.

Don't try to criticize the program itself. This is just a test for educational purposes. I set this up on purpose so that I would have a double free bug. The overloaded assignment operator is meant to prevent that. And it fails the second test... Why it does that?

Galaxy
  • 2,363
  • 2
  • 25
  • 59
  • `Test t2 = t1;` is not an assignment. It's copy construction equal to `Test t2(t1);` You did not provide `Test(const Test&)`, so compiler generates one for you. Unforunately, compiler-generated special members are not suited to correctly handle deep copying – Fureeish Oct 14 '18 at 21:42
  • 1
    Assignment is not initialisation. –  Oct 14 '18 at 21:43
  • ... And the haters just keep coming. – Galaxy Dec 24 '18 at 20:21

1 Answers1

-4

It is because initialization is not the same as assignment. So the overloaded assignment operator is not being called. The copy constructor is being called in Main 2. And because this class Test does not define an overloaded copy constructor, the default copy constructor is used, which does a member-by-member copy.

I'll demonstrate it by adding a copy constructor:

class Test {
  public:
    Test() : ptr{new int(0)}, val{0}, id{count++} {}

    Test(int ptr_val, int new_val) : ptr{new int(ptr_val)}, val{new_val}, id{count++} {}

    ~Test()
    {
        delete ptr;
    }

    Test(const Test& rhs) : ptr{new int( *(rhs.ptr)  )}, val{rhs.val}, id{count++}
    {
        cout << "copy constructor called" << endl;
    }

    Test& operator=(const Test& rhs)
    {
        *ptr = *(rhs.ptr);
        val = rhs.val;
    }

    void setPtrVal(int ptr_val)
    {
        *ptr = ptr_val;
    }

    void setVal(int new_val)
    {
        val = new_val;
    }

    void printData() const
    {
        cout << "id  = " << id  << endl;
        cout << "val = " << val << endl;
        cout << "ptr = " << ptr << endl;
        cout << "*ptr = " << *ptr << endl << endl;
    }
  private:
    int* ptr;
    int val;
    int id;

    static int count;
};

int Test::count = 1;


int main()
{
    Test t1(10, 15);

    {
        Test t2 = t1;
        t2.printData();
    }

    t1.printData();

    return 0;
}

The output is as expected:

copy constructor called
id  = 2
val = 15
ptr = 0xea4c40
*ptr = 10

id  = 1
val = 15
ptr = 0xea4c20
*ptr = 10
Galaxy
  • 2,363
  • 2
  • 25
  • 59
  • The copy constructor (and assignment) does a member by member copy, not bit by bit. –  Oct 14 '18 at 21:46