Define Class Destructor Using the Tilde Operator in C++
-
Use the Tilde Operator
~
to Declare Class Destructor in C++ - Execute Code Before the Class Object Destruction in C++
- Conclusion
The tilde operator, represented by the symbol ~
, is a unary operator with diverse applications in C++. Its primary function is to complement bits, effectively inverting the bits of an integer.
However, when it comes to object-oriented programming, the tilde operator takes on a distinct role in the context of class destructors. Here, we’ll explore the significance of the tilde operator in handling the deallocation of a class object’s resources.
Use the Tilde Operator ~
to Declare Class Destructor in C++
A class destructor stands as a specialized member function responsible for the cleanup process when a class object is no longer needed. Unlike class constructors, which initialize object states, a class typically possesses only one destructor.
The declaration of a class destructor involves utilizing the tilde (~
) operator followed by the class name, symbolizing its unique role in resource management.
~className()
In scenarios where a class incorporates data members allocated on dynamic memory, explicitly defining the class destructor becomes imperative. The destructor serves as the designated space for deallocating these dynamic resources.
It ensures a streamlined and efficient cleanup process. Failure to define an appropriate destructor may result in memory leaks and undesirable resource retention.
Let’s see a practical example to illustrate the application of the tilde operator in the definition of a class destructor.
Consider the following CustomString
class, designed as a wrapper for the standard C++ string (std::string
). This class features two constructors, including a copy constructor, emphasizing the nuanced scenarios that necessitate explicit destructor definition.
#include <iostream>
#include <string>
#include <vector>
using std::cout;
using std::endl;
using std::string;
using std::vector;
class CustomString {
public:
explicit CustomString(const string &s = string()) : str(new string(s)) {}
CustomString(const CustomString &p) : str(new string(*p.str)) {}
~CustomString() { delete str; }
string &getString() { return *str; };
private:
string *str;
};
int main() {
CustomString str1("Hello There!");
CustomString str2(str1);
cout << "str1: " << str1.getString() << endl;
cout << "str2: " << str2.getString() << endl << endl;
}
Output:
str1: Hello There!
str2: Hello There!
In this example, the CustomString
class showcases the tilde (~
) operator in action. Let’s break down each part of the code.
Firstly, the CustomString
class is declared with a private data member string *str
, which is a pointer to a dynamically allocated string.
The class features two constructors: an explicit constructor that takes a constant reference to a string (const string &s
) and dynamically allocates memory for the string, and a copy constructor (CustomString(const CustomString &p)
) that creates a deep copy of another CustomString
object.
The important aspect here is the class destructor, declared with the tilde operator (~CustomString()
). This destructor is responsible for releasing the dynamically allocated memory pointed to by str
when an object of the CustomString
class goes out of scope.
The delete str;
statement in the destructor ensures proper cleanup, preventing memory leaks.
In the main()
function, two instances of the CustomString
class, namely str1
and str2
, are created.
The first one, str1
, is initialized with a string using the explicit constructor. The second one, str2
, is then instantiated by invoking the copy constructor and passing str1
as an argument.
Finally, the program prints the contents of both str1
and str2
using the getString()
member function, demonstrating the successful copying of the string and proper cleanup through the destructor.
Execute Code Before the Class Object Destruction in C++
As previously stated, a class destructor is designed to handle the cleanup of resources associated with a class object before it goes out of scope. The previous method, as presented, involves explicit declaration and definition of a destructor using the tilde (~
) operator.
However, not all class members require dynamic memory allocation, and they might be regular data types declared on the program stack. In such cases, the compiler automatically generates a synthesized destructor if the programmer does not explicitly declare one.
This synthesized destructor ensures that the class members are appropriately cleaned up when the object is destroyed.
The code below introduces the StringArray
class, which includes both dynamic and stack-allocated members. The class has a dynamic integer array (init
) and a vector of strings (vec
).
The constructor initializes these members, and the destructor is responsible for deallocating the dynamic array and printing a corresponding message.
#include <iostream>
#include <string>
#include <vector>
using std::cout;
using std::endl;
using std::string;
using std::vector;
class StringArray {
public:
StringArray() : vec(), init(new int[10]){};
~StringArray() {
delete[] init;
cout << "printed from destructor" << endl;
}
string getString(int num) {
if (vec.at(num).c_str()) {
return vec.at(num);
} else {
return string("NULL");
}
};
void addString(const string& s) { vec.push_back(s); };
uint64_t getSize() { return vec.size(); };
private:
vector<string> vec;
int* init;
};
int main() {
StringArray arr;
arr.addString("Hello there 1");
arr.addString("Hello there 2");
cout << "size of arr: " << arr.getSize() << endl;
cout << "arr[0]: " << arr.getString(0) << endl;
}
Output:
size of arr: 2
arr[0]: Hello there 1
printed from destructor
Here, in the constructor (StringArray()
), the class initializes a vector of strings (vec
) and dynamically allocates an integer array (init
) of size 10
. This array is intended to store integers, serving as an illustrative example of dynamic memory allocation within the class.
The crucial aspect of this code lies in the destructor of the StringArray
class (~StringArray()
). This destructor is responsible for executing code before an object of the class is destroyed.
In this case, it deallocates the dynamically allocated integer array using the delete[]
operator. Additionally, a message is printed to the console, indicating that the destructor is being invoked.
This demonstrates the ability to perform custom cleanup actions and provide feedback when an object goes out of scope.
Moving to the main()
function, an instance of the StringArray
class named arr
is created, and two strings are added to the vector using the addString
method. We then print the size of the array and the first element of the vector.
As the main()
function concludes, the arr
object goes out of scope, which triggers the destructor to execute the cleanup code. This results in the deallocation of the dynamic integer array and the printing of the corresponding message to the console.
Conclusion
The tilde operator in C++ serves a dual purpose, from fundamental bitwise operations to its significant role in managing resources through class destructors.
Understanding its applications is essential for programmers seeking to write efficient, resource-friendly code. The practical examples presented in this article showcase the importance of the tilde operator in ensuring proper resource management and maintaining code integrity in C++ programs.
Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.
LinkedIn Facebook