Beginner's Guide
    First Steps
    Input & Output
    Basic Custom Types
    Diagnostics
    Standard Library
    Code Organization
    Powerful Custom Types
    Generic Programming
    Memory Management
    Software Design Basics

    Destructors (Basics)Destructors (Basics)Destructors

    Special Member Functions Special Members

    T::T() default constructor runs when new T object is created
    T::T(param…) special constructor runs when new T object is created with argument(s)
    T::~T() destructor runs when existing T object is destroyed

    The compiler generates a default constructor and a destructor if we don't define them ourselves.

    There are a four more special members that we will learn about.

    • copy constructor  T::T(T const&)
    • copy assignment operator  T& T::operator = (T const&)
    • move constructor  T::T(T &&)
    • move assignment operator  T& T::operator = (T &&)

    They are also automatically generated by the compiler and don't need to be user-defined in many/most cases.

    RAII

    Resource Acquisition Is Initialization

    • object construction: acquire resource
    • object destruction: release resource
    Example: std::vector

    Each vector object is owner of a separate buffer on the heap where the actual content is stored.

    This buffer is allocated on demand and de-allocated if the vector object is destroyed.

    vector<int> v {0,1,2,3,4};

    Ownership

    An object is said to be an owner of a resource (memory, file handle, connection, thread, lock, …) if it is responsible for its lifetime (initialization/creation, finalization/destruction).

    User-Defined Constructor & Destructor User-Defined Destructor User-Defined

    class Point {  };
    
    class T { int i_ = 0; std::vector<int> v_; std::vector<Point> w_; public: T() { std::cout << "constructor"; } ~T() { std::cout << "destructor"; } // more member functions … };
    if() {
      
      T x;  // prints 'constructor'
      
    }  // prints 'destructor'

    Execution Order on Destruction

    After the destructor body has run the destructors of all data members are executed in declaration order. This happens automatically and cannot be changed (at least not easily - this is C++ after all and there is of course a way to circumwent almost anything) .

    x goes out of scope → executes ~T():

    • std::cout << "destructor\n";
    • x's data members are destroyed:
      • i_ is destroyed; fundamental types don't have a destructor
      • v_ is destroyed → executes ~vector<int>():
        • destroys int elements in its buffer; (fundamental type → no destructor)
        • deallocates buffer on the heap
        • v_'s data members are destroyed
      • w_ is destroyed → executes ~vector<Point>():
        • destroys Point elements in its buffer
        • each ~Point() destructor is executed
        • deallocates buffer on the heap
        • w_'s data members are destroyed

    Example: Logging with RAII Example: RAII Logging Example: Logging

    • constructor of Device gets pointer to a UsageLog object
    • UsageLog can be used to record actions during a Device object's lifetime
    • destructor informs UsageLog if a Device is no longer present
    • the UsageLog could also count the number of active devices, etc.
    class File {  };
    class DeviceID {  };
    
    class UsageLog { public: explicit UsageLog(File const&); void armed(DeviceID); void disarmed(DeviceID); void fired(DeviceID); };
    class Device { DeviceID id_; UsageLog* log_; public: explicit Device(DeviceId id, UsageLog* log = nullptr): id_{id}, log_{log}, { if(log_) log_->armed(id_); } ~Device() { if(log_) log_->disarmed(id_); } void fire() { if(log_) log_->fired(id_); } };
    int main() {
      File file {"log.txt"}
      UsageLog log {file};
      
      Device d1 {DeviceID{1}, log};
      d1.fire(); 
      {
        Device d2 {DeviceID{2}, log};
        d2.fire(); 
      }
      d1.fire(); 
    }
    log.txt
    device 1   armed
    device 1   fired
    device 2   armed
    device 2   fired
    device 2   disarmed
    device 1   fired
    device 1   disarmed

    The Rule of Zero Rule of Zero

    Avoid writing special member functions unless you need to do RAII-style resource management.

    The compiler generated default constructor and destructor are sufficient in many cases.

    Initialization doesn't require constructors.

    Most data members can be initialized with Member Initializers.

    You almost never need to write destructors.

    Before C++11 custom classes with explicit manual memory management were very common. However, in modern C++ memory management strategies are mostly (and should be) encapsulated in dedicated classes (containers, smart pointers, allocators, …).