Destructor (computer programming)In object-oriented programming, a destructor (sometimes abbreviated dtor[1]) is a method which is invoked mechanically just before the memory of the object is released.[2] It can happen either when its lifetime is bound to scope and the execution leaves the scope, when it is embedded in another object whose lifetime ends, or when it was allocated dynamically and is released explicitly. Its main purpose is to free the resources (memory allocations, open files or sockets, database connections, resource locks, etc.) which were acquired by the object during its life and/or deregister from other entities which may keep references to it. Destructors are necessary in resource acquisition is initialization (RAII). With most kinds of automatic garbage collection algorithms, the releasing of memory may happen a long time after the object becomes unreachable, making destructors unsuitable for time-critical purposes. In these languages, the freeing of resources is done through an lexical construct (such as try-finally, Python's Syntax
Language detailsC++The destructor has the same name as the class, but with a tilde (~) before it.[2] For example, a class called In inheritance hierarchies, the declaration of a virtual destructor in the base class ensures that the destructors of derived classes are invoked properly when an object is deleted through a pointer-to-base-class. Objects that may be deleted in this way need to inherit a virtual destructor. A destructor should never throw an exception.[8] Non-class scalar types have what's called a pseudo-destructor which can be accessed by using int f() {
int a = 123;
using T = int;
a.~T();
return a; // undefined behavior
}
In older versions of the standard, pseudo-destructors were specified to have no effect, however that was changed in a defect report to make them end the lifetime of the object they are called on.[9] Objects which cannot be safely copied and/or assigned should be disabled from such semantics by declaring their corresponding functions as deleted. A detailed description of this method can be found in Scott Meyers' popular book, Effective Modern C++ (Item 11: "Prefer deleted functions to private undefined ones."[10]). If they are marked Exampleimport std;
class Foo {
private:
char data[];
friend struct std::formatter<Foo>;
public:
// Constructor
explicit Foo(const char* s = ""):
data{new char[std::strlen(s) + 1]} {
std::strcpy(data, s);
}
Foo(const Foo& other) = delete("Copy construction disabled");
Foo& operator=(const Foo& other) = delete("Copy assignment disabled");
// Destructor
~Foo() {
delete[] data;
}
};
template <>
struct std::formatter<Foo> {
constexpr auto parse(std::format_parse_context& ctx) -> const char* {
return ctx.end();
}
template <typename FormatContext>
auto format(const Foo& foo, FormatContext& ctx) -> FormatContext::iterator {
return std::format_to(ctx.out(), "{}", foo.data);
}
};
int main(int argc, char* argv[]) {
Foo foo("Hello from the stack!");
std::println("{}", foo);
Foo* foo = new Foo("Hello from the heap!");
std::println("{}", *foo);
delete foo;
}
By using smart pointers with the "Resource Acquisition is Initialization" (RAII) idiom, manually defining destructors or calling resource cleanup can be bypassed. Other languages like Java and C# include a import std;
class Foo {
private:
std::unique_ptr<char[]> data;
friend struct std::formatter<Foo>;
public:
// Constructor
explicit Foo(const char* s = ""):
data{std::make_unique<char[]>(std::strlen(s) + 1)} {
std::strcpy(data.get(), s);
}
Foo(const Foo& other) = delete("Copy construction disabled");
Foo& operator=(const Foo& other) = delete("Copy assignment disabled");
// Destructor is automatically handled by unique_ptr
~Foo() = default;
};
template <>
struct std::formatter<Foo> {
constexpr auto parse(std::format_parse_context& ctx) -> const char* {
return ctx.end();
}
template <typename FormatContext>
auto format(const Foo& foo, FormatContext& ctx) -> FormatContext::iterator {
return std::format_to(ctx.out(), "{}", foo.data.get());
}
};
int main(int argc, char* argv[]) {
Foo foo("Hello from the stack!");
std::println("{}", foo);
std::unique_ptr<Foo> foo = std::make_unique<Foo>("Hello from the heap!");
std::println("{}", *foo);
}
C#Destructors in C# are not manually called or called by a ![]() using System;
class MyClass
{
private string resource;
public MyClass(string resourceName)
{
resource = resourceName;
}
~MyClass()
{
Console.WriteLine($"Destructor called to clean up resource: {resource}");
// cleanup code
}
}
class Program
{
static void Main(string[] args)
{
MyClass obj = new MyClass("Sample Resource");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Program finished");
}
}
C# also has a "dispose" pattern in which the class must implement the interface using System;
class MyClass : IDisposable
{
private string resource;
private bool disposed = false; // To detect redundant calls to Dispose()
public MyClass(string resourceName)
{
resource = resourceName;
}
~MyClass()
{
Console.WriteLine("Destructor called");
Dispose(false);
}
public void Dispose()
{
Console.WriteLine("Disposer called");
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
Console.WriteLine("Disposing managed resources.");
}
Console.WriteLine("Disposing unmanaged resources.");
disposed = true;
}
}
}
class Program
{
static void Main(string[] args)
{
using (Destructible d = new Destructible())
{
Console.WriteLine("Using Destructible.");
// ...
}
// after using-with-resources, d.Dispose() will be called
}
}
C with GCC extensionsThe GNU Compiler Collection's C compiler comes with 2 extensions that allow implementing destructors:
JavaJava provides 2 interfaces that implement destructors, final class Destructible implements AutoCloseable {
@Override
public void close() {
// cleanup code
}
}
public final class Example {
try (Destructible d = new Destructible()) {
System.out.println("Using Destructible.");
// ...
}
// after try-with-resources, d.close() will be called
}
Prior to Java 7, a "try-finally" block was used. final class Destructible {
public void close() {
// cleanup code
}
}
public final class Example {
try {
Destructible d = new Destructible();
System.out.println("Using Destructible.");
} finally {
d.close()
}
}
Historically, Java used class ParentFinalizerExample { ... }
class FinalizerExample extends ParentFinalizerExample {
@Override
protected void finalize() throws Throwable {
try {
System.out.println("finalize() called, cleaning up...");
} finally {
super.finalize(); // Always call super.finalize() to clean parent classes
}
}
}
public class Example {
public static void main(String[] args) {
FinalizerExample obj = new FinalizerExample ();
obj = null;
System.gc(); // Requests garbage collection (not guaranteed)
}
}
Java also supports classes import java.lang.ref.Cleaner;
import java.lang.ref.Cleaner.Cleanable;
class Resource {
private static final Cleaner cleaner = Cleaner.create();
static class State implements Runnable {
private boolean cleaned = false;
@Override
public void run() {
cleaned = true;
System.out.println("Cleaned using Cleaner");
}
}
private final State state;
private final Cleanable cleanable;
public Resource () {
this.state = new State();
this.cleanable = cleaner.register(this, state);
}
public void cleanup() {
System.gc(); // Request garbage collection (not guaranteed)
}
}
public class Example {
public static void main(String[] args) {
Resource resource = new Resource();
resource = null;
resource.cleanup();
}
}
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
class Resource {
private final String name;
public Resource(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class PhantomReferenceExample {
public static void main(String[] args) throws InterruptedException {
Resource resource = new Resource("My resource");
ReferenceQueue<Resource> queue = new ReferenceQueue<>();
PhantomReference<Resource> phantomRef = new PhantomReference<>(resource, queue);
resource = null;
System.gc();
Reference<? extends Resource> ref = queue.poll();
if (ref != null) {
System.out.printf("Object is ready to be collected: %s%n", ((PhantomReference<?>)ref).get());
}
}
}
Unlike to PythonPython supports destructors and has a class Destructible:
def __init__(self, name: str) -> 'Destructible':
self.name = name
print(f"Created Destructible: {self.name}")
def __del__(self) -> None:
print(f"Destructor called for: {self.name}")
if __name__ == "__main__":
d: Destructible = Destructible("My name")
print(f"Using Destructible: {d.name}")
del d
Much like Java and C#, Python has a try-with-resources block, called a from typing import Optional
class Destructible:
def __init__(self, name: str) -> None:
self.name: str = name
def __enter__(self) -> "Destructible":
print(f"Entering context (allocating resource: {self.name})")
return self
def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception] None, exc_tb: Optional[type]) -> None:
print(f"Exiting context (cleaning up resource: {self.name})")
if __name__ == "__main__":
with Destructible("Resource A") as d:
print(f"Using resource {d.name} inside context")
# Most Python standard library resources support with blocks:
with open(file_path, "r") as file:
print("Reading the file content:")
content: str = file.read()
print(content)
RustRust does not have destructors in the sense of object-oriented programming, but a It is not possible to destroy objects explicitly through a use std::mem;
struct Destructible {
name: String,
}
impl Destructible {
fn new(name: String) -> Self {
Destructible { name }
}
}
impl Drop for Destructible {
fn drop(&mut self) {
println!("Dropping Destructible: {}", self.name);
}
}
fn main() {
{
let resource_a: Destructible = Destructible::new(String::from("Resource A"));
println!("Using Destructible.");
} // <--- resource_a goes out of scope here, `drop()` is called automatically
let resource_b: Destructible = Destructible::new(String::from("Resource B"));
println!("Dropping Destructible prematurely.");
mem::drop(resource_b);
}
While lifetimes control the validity of references, they do not determine when XojoDestructors in Xojo (REALbasic) can be in one of two forms. Each form uses a regular method declaration with a special name (with no parameters and no return value). The older form uses the same name as the Class with a ~ (tilde) prefix. The newer form uses the name Class Foobar // Old form Sub ~Foobar() End Sub // New form Sub Destructor() End Sub End Class See also
References
|