Virtual destructor

Virtual destructor is used to prevent memory leak in the context of polymorphism.

Let’s start with a simple example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Base {
public:
Base() {
std::cout << "Base constructor called\n";
}

~Base() {
std::cout << "Base deconstructor called\n";
}
};

class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor called\n";
}

~Derived() {
std::cout << "Derived deconstructor called\n";
}
};

int main() {
Base* b = new Base();
delete b;

std::cout << "----------\n";

Derived* d = new Derived();
delete d;
}

output:

1
2
3
4
5
6
7
Base constructor called
Base deconstructor called
----------
Base constructor called
Derived constructor called
Derived deconstructor called
Base deconstructor called

When we define a Base type pointer and the pointer points to Derived, things are different. The ~Derived() is not called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Base {
public:
Base() {
std::cout << "Base constructor called\n";
}

~Base() {
std::cout << "Base deconstructor called\n";
}
};

class Derived : public Base {
public:
Derived() {
std::cout << "Derived constructor called\n";
}

~Derived() {
std::cout << "Derived deconstructor called\n";
}
};

int main() {
Base* b = new Derived();
delete b;
}

output:

1
2
3
Base constructor called
Derived constructor called
Base deconstructor called

This is when we might have memory leak in C++. Suppose we have data created in Derived, the destructor is responsible for cleaning up the memeory, however, it never gets called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Base {
public:
Base() {
std::cout << "Base constructor called\n";
}

~Base() {
std::cout << "Base deconstructor called\n";
}
};

class Derived : public Base {
private:
int* m_data;
public:
Derived() {
m_data = new int[5];
std::cout << "Derived constructor called\n";
}

~Derived() {
std::cout << "Derived deconstructor called\n";
delete[] m_data;
}
};

int main() {
Base* b = new Derived();
delete b;
}

All we need to do is to make Base destructor virtual. That will fix this problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Base {
public:
Base() {
std::cout << "Base constructor called\n";
}

virtual ~Base() {
std::cout << "Base deconstructor called\n";
}
};

class Derived : public Base {
private:
int* m_data;
public:
Derived() {
m_data = new int[5];
std::cout << "Derived constructor called\n";
}

~Derived() {
std::cout << "Derived deconstructor called\n";
delete[] m_data;
}
};

int main() {
Base* b = new Derived();
delete b;
}

output:

1
2
3
4
Base constructor called
Derived constructor called
Derived deconstructor called
Base deconstructor called

References

Author

Joe Chu

Posted on

2023-11-17

Updated on

2024-03-24

Licensed under

Comments