Beginner's Guide
    First Steps
    Input & Output
    Custom Types – Part 1
    Diagnostics
    Standard Library – Part 1
    Function Objects
    Standard Library – Part 2
    Code Organization
    Custom Types – Part 2
    Generic Programming
    Memory Management
    Software Design Basics

    Destructors Destructors 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

    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};

    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).

    = variables refer to objects themselves, i.e., they are not just references/pointers

    This is the default behavior for fundamental types (int, double, etc.) in almost all programming languages and also the default for user-defined types in C++:

    • deep copying: produces a new, independent object; object (member) values are copied
    • deep assignment: makes value of target equal to that of source object
    • deep ownership: member variables refer to objects with same lifetime as containing object
    • value-based comparison: variables compare equal/less/… if their values are equal/less/…

    Because the lifetime of members is tied to its containing object, there is no need for a garbage collector.

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

    class Point {  };
    class Test {
      int i_ = 0;
      std::vector<int> v_;
      std::vector<Point> w_;
    public:
      Test() {     std::cout << "constructor";   }
      ~Test() {     std::cout << "destructor";   }
      // more member functions …
    };
    if() {
      
      Test 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 ~Test():

    • 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

    = (try to) write zero special member functions

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

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

    Initialization doesn't always require writing 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, …).