2

I am trying to understand the concept of copy constructor. I used this example:

#include <iostream>

using namespace std;

class Line
{
public:
    int GetLength();
    Line(int len);
    Line(const Line &obj);
    ~Line();

private:
    int *ptr;
};

Line::Line(int len)
{
    ptr = new int;
    *ptr = len;
};

Line::Line(const Line &obj)
{
    cout << "Copying... " << endl;
    ptr = new int;
    *ptr = *obj.ptr;
};

Line::~Line()
{
    delete ptr;
};

int Line::GetLength()
{
    return *ptr;
}

int main()
{
    Line line1 = Line(4);
    cout << line1.GetLength() << endl;

    Line line2 = line1;
    line1.~Line();

    cout << line2.GetLength() << endl;

    return 0;
}

The question is, why do I get runtime error here? If I defined a copy constructor which allocates memory for the new ptr, and assigned the line1 to line2, doesn't that mean that those two are separate objects? By destructing line1, I obviously mess up line2 as well, or am I using the destructor call wrong?

omegasbk
  • 826
  • 1
  • 10
  • 24

2 Answers2

7

You called the destructor in this statement

line1.~Line();

which deleted the memory allocated for ptr

Line::~Line()
{
    delete ptr;
};

However the object line1 is alive because it has automatic storage duration. So after exiting main the destructor for the object will be called one more and as result it will try to delete the memory pointed to by ptr that was already deleted explicitly.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 4
    Technically it is undefined behaviour as soon as the destructor is called for a second time ([class.dtor]/15) – M.M Feb 27 '16 at 10:44
0
line1.~Line();

Manually calling a destructor is useful only if you use placement new.

I don't understand what gave you the idea to call the destructor manually in this program. You don't really want to know such a low-level memory-management mechanism when you are still new to the language, but for the sake of completeness, it would work like this:

int main()
{
    // provide static memory with enough space for one Line object:
    char buffer[sizeof(Line)];

    // create a Line object and place it into buffer:
    Line* line1 = new (buffer) Line(4);

    cout << line1->GetLength() << endl;

    Line line2 = *line1;

    // manually call the destructor:
    line1->~Line();

    cout << line2.GetLength() << endl;

    // - no delete necessary because buffer disappears automatically
    // - no automatic destructor call

    return 0;
}

Your code, however, results in an attempt to call the destructor of line1 twice. First manually, then automatically when the object's scope ends, i.e. at the end of main. This is undefined behaviour. See Does explicitly calling destructor result in Undefined Behavior here?

The question is, why do I get runtime error here?

Because undefined behaviour means that your program can do or not do anything. The runtime error is not guaranteed, nor are you guaranteed any deterministic behaviour at all.

Community
  • 1
  • 1
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • I thought that manually triggering destructor causes freeing of the memory held by the object. I wanted to be sure that I have a new object once when I assign line1 to line2. – omegasbk Feb 27 '16 at 11:57
  • 1
    @omegasbk: Destructors never free the memory occupied by their own object. That's not their purpose. The word "destructor" itself may be confusing; it should technically be named "do_stuff_before_object_is_destructed_by_someone_else". – Christian Hackl Feb 27 '16 at 12:04
  • A-ha, makes sense! Thanks! – omegasbk Feb 27 '16 at 12:50
  • 1
    @omegasbk: Other such funny obversations in the C++ language: `delete` does not delete its argument, `std::move` does not move and `std::forward` does not forward :) – Christian Hackl Feb 27 '16 at 12:52