Vector Destructor in C++

Naila Saad Siddiqui Oct 26, 2023
  1. Destructors in C++
  2. Destructors of Vectors and Strings in C++
  3. Conclusion
Vector Destructor in C++

When working with dynamic data structures like vectors and strings, it’s essential to manage memory efficiently to avoid resource leaks. Destructors play a crucial role in this process, ensuring that resources are properly deallocated when objects go out of scope.

In this article, we’ll explore the concept of destructors and their significance when creating vectors and strings in C++.

Destructors in C++

In Object-Oriented Programming (OOP), a destructor is a member function associated with each class, and it plays a pivotal role in memory management and resource cleanup. In C++, these special functions are automatically executed whenever an object of the class is deleted or reaches the end of its lifetime.

Destructors are also involved in managing variables created on heap memory, ensuring that memory is properly freed up for future use.

Destructors are defined with a unique naming convention. They bear the same name as the class to which they belong but with a tilde (~) symbol as a prefix.

For example, if you have a class named MyClass, its destructor would be named ~MyClass.

In a class, every object created using a constructor is eventually deleted by a destructor, following the reverse order of their creation.

Here’s the syntax for defining a destructor in C++:

~<className>() {
  // Destructor code
}

Let’s illustrate this with a practical example:

#include <iostream>
using namespace std;

class DestructorExample {
 public:
  DestructorExample() { cout << "In Constructor of DestructorExample" << endl; }

  ~DestructorExample() { cout << "In Destructor of DestructorExample"; }
};

int main() {
  DestructorExample t;
  return 0;
}

When you run this code, it produces the following output:

In Constructor of DestructorExample
In Destructor of DestructorExample

In this example, the constructor (DestructorExample()) is automatically invoked when an instance of the class is created. Conversely, the destructor (~DestructorExample()) is automatically called when the object t goes out of scope, ensuring that any resources allocated by the object are appropriately released.

Destructors in C++ provide a fundamental mechanism for proper resource management, making sure that memory is deallocated and resources are released, which helps prevent memory leaks and other resource-related issues. This level of control over resource management is one of the key features that sets C++ apart as a robust and efficient programming language.

Destructors of Vectors and Strings in C++

In C++, the Standard Library provides two powerful data structures: std::vector and std::string. The good news is that these classes have their destructors automatically invoked when their objects go out of scope.

These automatic destructors are crucial in maintaining the integrity of your code and keeping resource management efficient.

Let’s consider std::vector first. When a std::vector goes out of scope, its destructor is called automatically, ensuring that all the elements contained within it are properly destroyed.

For instance:

#include <vector>

int main() {
    std::vector<int> numbers;
    numbers.push_back(1);
    numbers.push_back(2);
    // The vector's destructor is automatically called when 'numbers' goes out of scope
    return 0;
}

In the main function, a std::vector named numbers is created to hold integers. Two integers, 1 and 2, are added to the vector using the push_back method.

Since the vector is a local variable in the main function, it goes out of scope when the function returns.

The essential point to note here is that when the numbers vector goes out of scope, its destructor is automatically called. The destructor of the vector is responsible for freeing the memory used by the vector, which includes the memory allocated for the integer elements.

Therefore, you don’t need to explicitly manage the cleanup of the vector and its elements, as the C++ Standard Library ensures proper resource management.

The same principle applies to std::string. When a std::string object reaches the end of its lifetime or goes out of scope, its destructor is automatically called to release any dynamically allocated memory used to store its character data.

Handle Pointers in a std::vector

A common scenario in C++ is working with a std::vector of pointers to objects, like std::vector<MyClass*>. In this situation, the vector’s destructor only manages the memory used by the vector itself (the pointers).

It does not automatically invoke the destructors of the objects pointed to by those pointers. In such cases, you must explicitly delete the objects pointed to by the pointers.

Let’s delve into an example that illustrates this concept:

#include <iostream>
#include <vector>

using namespace std;

class MyClass {
 public:
  MyClass() { cout << "Object Created" << endl; }

  ~MyClass() { cout << "Object Destroyed" << endl; }
};

int main() {
  vector<MyClass*> testVec;

  // Creating the objects
  MyClass* p = NULL;
  for (int i = 0; i < 3; i++) {
    p = new MyClass();
    testVec.push_back(p);
  }

  // Destroying the objects
  for (auto p : testVec) {
    delete p;
  }
  testVec.clear();

  return 0;
}

The code begins by defining a class named MyClass with a constructor and destructor, which print messages to the console upon object creation and destruction.

In the main function, a std::vector called testVec is created to hold pointers to MyClass objects. Within a loop, three MyClass objects are dynamically allocated on the heap and added to the vector.

Each time an object is added, an Object Created message is displayed, showcasing object creation.

In order to prevent memory leaks, the code includes a subsequent loop to iterate over the testVec vector and explicitly deletes each object using the delete operator. This is essential for deallocating the memory on the heap occupied by the MyClass objects.

Once the objects are deleted, the vector’s clear method is called to remove all elements, ensuring that no dangling pointers or memory issues persist.

In a nutshell, we explicitly delete each object pointed to by the vector elements before the vector goes out of scope to prevent memory leaks. This is essential because the vector’s destructor does not automatically handle the deletion of objects when pointers are involved.

It only takes care of the memory used by the pointers. As a result, the memory allocated on the heap for these objects is released, and the output demonstrates this process:

Object Created
Object Created
Object Created
Object Destroyed
Object Destroyed
Object Destroyed

This process of explicit deletion of objects pointed to by pointers in the vector ensures that the memory allocated on the heap is properly reclaimed, preventing memory leaks.

Handle Pointer Members Inside a Class

Now, when a class contains a pointer member, such as:

class A {
  ClassB* B;
};

The destructor of class A only manages the memory used by A itself, including the memory for the pointer B. It does not automatically invoke the destructor of ClassB.

If A is destroyed, it doesn’t automatically destroy the object it points to (ClassB). You must explicitly handle the cleanup of ClassB objects, typically in the destructor of A or before A goes out of scope.

Here’s an example of how you might define a destructor for A to clean up the ClassB object it points to:

class A {
  ClassB* B;
public:
  A() : B(nullptr) { }
  ~A() {
    delete B; // Clean up the ClassB object
  }
};

Here’s a complete working example:

#include <iostream>

class ClassB {
public:
    ClassB() {
        std::cout << "ClassB constructor called." << std::endl;
    }

    ~ClassB() {
        std::cout << "ClassB destructor called." << std::endl;
    }
};

class A {
    ClassB* B;

public:
    A() : B(nullptr) {}

    void setB(ClassB* b) {
        B = b;
    }

    ~A() {
        delete B; // Clean up the ClassB object
    }
};

int main() {
    A objectA;
    ClassB* b = new ClassB();
    objectA.setB(b);

    // When objectA goes out of scope, its destructor will be called,
    // which, in turn, will delete the ClassB object.
    return 0;
}

Output:

ClassB constructor called.
ClassB destructor called.

In this example, we’ve defined a simple ClassB class with a constructor and destructor for demonstration purposes. The A class contains a pointer to a ClassB object, and its destructor is responsible for cleaning up the ClassB object when the A object goes out of scope.

It defines two classes, ClassB and A, with constructors and destructors to demonstrate object creation and cleanup. The A class contains a private member B, which is a pointer to a ClassB object.

To associate the ClassB object with an A object, a public member function, setB, is used.

When the main function creates an instance of A and dynamically allocates a ClassB object, the program showcases how proper resource management works. As the main function completes, the destructor of A is invoked, which, in turn, deletes the ClassB object pointed to by B, ensuring efficient resource cleanup and memory deallocation.

Conclusion

Destructors are a pivotal element of C++ resource management, ensuring that dynamically allocated resources are efficiently released when objects go out of scope. Understanding how automatic destructors work for std::vector and std::string is crucial for efficient resource management.

Moreover, when dealing with vectors of pointers, it’s essential to remember that the vector’s destructor will not automatically handle the deletion of objects. By mastering these concepts, you can write C++ code that is not only efficient but also robust in handling resource management, minimizing the risk of memory leaks and resource-related issues.

Related Article - C++ Destructor