Constructors and Destructors - krishnaramb/cplusplus GitHub Wiki

Introduction

// program: 1
#include <iostream>
#include <string>
using namespace std;

string ident(string arg)        // string passed by value (copied into arg)
{
    return arg;                 // return str ing (move the value of arg out of ident() to a caller)
}


int main ()
{
    string s1 {"Adams"};        // initialize str ing (constr uct in s1).
    s1 = ident(s1);            // copy s1 into ident()
                                // move the result of ident(s1) into s1;
                                // s1’s value is "Adams".
    string s2 {"Pratchett"};    // initialize str ing (constr uct in s2)
    s1 = s2;                    // copy the value of s2 into s1
                                // both s1 and s2 have the value "Pratchett".
}

Several functions are used here in program: 1.

  • A constructor initializing a string with a string literal (used for s1 and s2)
  • A copy constructor copying a string (into the function argument arg)
  • A move constructor moving the value of a string (from arg out of ident() into a temporary variable holding the result of ident(s1))
  • A move assignment moving the value of a string (from the temporary variable holding the result of ident(s1) into s1)
  • A copy assignment copying a string (from s2 into s1)
  • A destructor releasing the resources owned by s1, s2, and the temporary variable holding the result of ident(s1)
class X {
  X(Sometype);           // ‘‘ordinary constructor’’: create an object
  X();                   //default constructor
  X(const X&);           // copy constructor
  X(X&&);                //move constructor
  X& operator=(const X&);// copy assignment: clean up target and copy
  X& operator=(X&&);     // move assignment: clean up target and move
  ˜X();                  //destructor: clean up
  // ...
};
  • There are five situations in which an object is copied or moved:
    • As the source of an assignment
    • As an object initializer
    • As a function argument
    • As a function return value
    • As an exception
  • In all cases, the copy or move constructor will be applied
  • In addition to the initialization of named objects and objects on the free store, constructors are used to initialize temporary objects and to implement explicit type conversion

Constructors and invariants

  • A member with the same name as its class is called a constructor
  • A constructor declaration specifies an argument list but has no return type
  • A constructor's job is to initilize an obj of its class where initialization must establish a class invariant-that is something that must hold whenever a member function is called
  • If the constructor cannot establish the invariant, no obj is created and the constructor must ensure that no resource are leaked
  • A resource is anything we need to acquire and eventually (explicitly or implicitly) give back(release) once we are finished with it
  • Example of resources are memory, locks, file handlers, and thread handles

Destructors and resources

  • some classes need a function that is guaranteed to be invoked when an obj is destroyed in a manner similar to the way a constructor is guaranteed to be invoked when an obj is created and such a function is called a destructor.
  • The destructor is the complement (~) of constructor
  • A destructor doesn't take an argument, and a class can have only one destructor.
  • Destructors are called implicitly when an automatic variable goes out of scope or when an object on the free store is deleted 🍁 🌹 🍀. see the following example
  • If a obj is constructed using new, it must be deleted using delete
  • Destructor typically clean up and releases the resources.
Vector∗ f(int s)
{
    Vector v1(s);
    // ...
    return new Vector(s+s);
}
void g(int ss)
{
    Vector∗ p = f(ss);
    // ...
    delete p;
}
  • Here, the Vector v1 is destroyed upon exit from f(). Also, the Vector created on the free store by f() using new is destroyed by the call of delete. In both cases, Vector’s destructor is invoked to free (deallocate) the memory allocated by the constructor.

Base and Member Destructions:

  • Constructors and destructors interact correctly with class hierarchies
  • A constructor builds a class object ‘‘from the bottom up’’::punch:
    [1] first, the constructor invokes its base class constructors,
    [2] then, it invokes the member constructors, and
    [3] finally, it executes its own body.
  • A destructor ‘‘tears down’’ an object in the reverse order(from top bottom):punch:
    [1] first, the destructor executes its own body,
    [2] then, it invokes its member destructors, and
    [3] finally, it invokes its base class destructors.
  • For example
// Example program
#include <iostream>
#include <string>


using namespace std;

class Person{
  public:
    Person(){
      cout<<"default const of Person called"<<endl;
    }
    ~Person(){
      cout<<"destructor of Person called"<<endl;
    }

};

class Student: public Person{
  public:
    Student(){
      cout<<"default const of student called"<<endl;
    }
    ~Student()
    {
      cout<<"destructor of student called" <<endl;
    }
};
int main ()
{
  Student s2;
  cout<<"end of main"<<endl;
}

/*
default const of Person called
default const of student called
end of main
destructor of student called
destructor of Person called
*/
int main ()
{
    Student * ps1= new Student;
    delete ps1;
    cout<<"end of main"<<endl;
}
/*default const of Person called
default const of student called
destructor of student called
destructor of Person called
end of main*/
int main ()
{
    Student * ps1= new Student[3];
    delete[] ps1;
    cout<<"end of main"<<endl;

}
/*default const of Person called
default const of student called
default const of Person called
default const of student called
default const of Person called
default const of student called
destructor of student called
destructor of Person called
destructor of student called
destructor of Person called
destructor of student called
destructor of Person called
end of main*/
int main ()
{
    Person * ps2 = new Student;
    delete ps2;  //note here, destructor of Person is only called
    cout<<"end of main"<<endl;
}
/*default const of Person called
default const of student called
destructor of Person called
end of main*/

Constructors and Destructors

  • We can specify how an object of a class is to be initialized by defining a constructor
  • To complement constructors, we can define a destructor to ensure ‘‘cleanup’’ at the point of destruction of an object (e.g., when it goes out of scope).
  • Some of the most effective techniques for resource management in C++ rely on constructor/destructor pairs
#include <iostream>
#include <string>
using namespace std;


/* purpose is evaluate destructor call  how automatic variavble vs free storage
1. if we have not used the destructor here, the compiler's default destructor
   would have run implicity when the obj is out of scope and it will destroy the
   object (it destroys variables sz and elem),but elem has pointer to a location in free
   store(heap) which will not be freed, so such type of program has memory leak
2. So, the work of destructor is to reclaim the memory from free store that has
   been allocated by construct when obj is created
3. Note, destructor will be called implicity when the obj is out of scope, or when
   obj in free store is deleted by keyword delete.


*/

class Vector{
    private:
        int sz;
        double *elem;
    public:
        Vector(){
             cout<<"Default construct called"<<endl;
             sz = 0;
             elem = nullptr;
        }
        Vector (int s)
        {
            cout <<"Integer argument constructor called"<<endl;
            sz =s;
            elem = new double[s];
            for (auto i = 0; i != sz; ++i)  //initialize all the values to zeros
                elem[i] = 0;  
        }


        Vector( const Vector &a) //copy-constructor  Vector V1(v2)
        {   
            cout<<"copy const called"<<endl;
            sz = a.sz;
            elem = new double[sz];
            for (auto i = 0; i != a.sz; ++i)
                elem[i] = a.elem[i];

        }
        /*Why must the copy assignment operator return a reference/const reference?
        Strictly speaking, the result of a copy assignment operator doesn't need to return
        a reference, though to mimic the default behavior the C++ compiler uses, it should
        return a non-const reference to the object that is assigned to (an implicitly
        generated copy assignment operator will return a non-const reference. I've seen
        a fair bit of code that returns void from copy assignment overloads, and
        I can't recall when that caused a serious problem. Returning void will prevent
        users from 'assignment chaining' (a = b = c;), and will prevent using the
        result of an assignment in a test expression
        */
       Vector & operator=(const Vector &a)  //copy assignment  v2 = v1
        {
            cout<<"copy-assignment called"<<endl;
            sz = a.sz;
            delete[] elem;
            elem = new double[sz];
            for (auto i = 0; i != sz; ++i)
                elem[i] = a.elem[i];
            return *this;

        }

       double &  operator[](int index)    // v1[20]
         {
             return elem[index];
         }

        ~Vector()
        {
            cout<<"destructor called"<<endl;
            delete[] elem;
        }
        int size()
        {
            return sz;
        }
};
/* returning ref of local variable (is in stack)--very bad */
Vector*func1(int sz)
{
    cout<<"entering inside func1"<<endl;
    Vector v1(sz);
    for(auto i = 0; i < sz; ++i)
        v1[i] = i+1;
    return &v1;  //very bad, we are returning the address of the local automatic variable
    /*soln
    1. return the obj by value(involves copy-constructor)
    2. Use of static variables
    3. Use new to create the obj is free store
    */
}

/* using static variable(is in data or BSS segment)*/
Vector *func2(int sz)
{
    cout <<"entering inside func2"<<endl;
    static Vector v2(sz);
     for(auto i = 0; i < sz; ++i)
        v2[i] = i+1;
    return &v2;
}

/*Direct return of the object--find out if copy-constructor is needed here?  Please refere ** */
Vector func3(int sz)
{
    cout<<"entering inside func3"<<endl;
    Vector v1(sz);
    for(auto i = 0; i < sz; ++i)
        v1[i] = i+1;
    return v1;
}

/* using new (is in free store i.e heap) */
Vector* func4(int sz)
{
    cout<<"entering inside func4"<<endl;
    Vector* v = new Vector(sz);
    return v;
}
/*how to dynamically declare an array of objects with a constructor in c++??
This can be done for obj with default ctors, like
Vector * v = new Vector[30];
But if you want to create an array of objs constructed using non-default constructors
Vector *v = new Vector(2)[30]; this is wrong
Better solution is use the standard container std::vector<Vector> v(100, Vector(20));*/


int main()
{
    cout<<"------------------------------------"<<endl;
    Vector v1, v2(3);
    cout<<"size of v1=" <<v1.size()<<", size of v2="<<v2.size()<<endl;
    cout<<"------------------------------------"<<endl;
    Vector * ptr1 = func1(5); //ptr is not the valid address now, it will cause segmentation fault if we use it
//    cout<<"size of ptr1-vector ="<< ptr1->size()<<"\n"<<endl;  //segmentation fault
    cout<<"------------------------------------"<<endl;
    Vector *ptr2 = func2(6);
    cout<<"size of ptr2-vector ="<< ptr2->size()<<endl;  
    for(auto i = 0; i < ptr2->size(); ++i)
        cout<<(*ptr2)[i];
    cout<<endl;
    cout<<"------------------------------------"<<endl;

    Vector v3 = func3(7);
    cout<<"size of v3="<<v3.size()<<endl;
    cout <<"v3=";
    for(auto i = 0; i < v3.size(); ++i)
        cout<<v3[i];
    cout<<endl;
    cout<<"------------------------------------"<<endl;

    Vector v4 = v3;  //this is very risky--refer *

    v3[1] = v3[2] = 0;
    cout<<"v3=";
    for(auto i = 0; i < v3.size(); ++i)
        cout<<v3[i];
    cout<<endl;

    cout<<"v4=";
    for(auto i = 0; i < v4.size(); ++i)
        cout<<v4[i];
    cout<<endl;
    cout<<"------------------------------------"<<endl;

    Vector* ptr_v5 = func4(8);
     cout<<"v5=";
    for(auto i = 0; i < ptr_v5->size(); ++i)
        cout<<(*ptr_v5)[i];
    cout<<endl;
    delete ptr_v5;  //we need to call here delete explicity
    cout<<"------------------------------------"<<endl;
    cout<<"end of main"<<endl;
/*
*. If you don't have copy-constructor, then compilers default constructor would have run
here. So, member wise copy of the member variables would have taken place.
Since its not the concrete class, only v4's sz and v3's sz will have separate location in
memory but v4's elem and v3's elem will point to the same memory location in free store(due
to addr copy). Hence we will have  the following issues:  
(i) change in v3's elem will reflect in v4's elem and vice-versa
(ii) other more drastic issue is, program will crash if destructor was used(why??).
=> notice that v3 and v4 are both local variables, and when they will
go out of scope, destructor will be invoked. When one of the destructor will get invoked,
it will clean up the free store pointed by its elem member.Now, when the destructor
of the other one runs, it tries to free already freed area of free store crashing the
 program.

**. when we have a call to `func3() `function as `Vector v1 = func3(10)`, we don't see
the copy-constructor being called because of RVO (Return Value Optimization).

output:
------------------------------------
Default construct called
Integer argument constructor called
size of v1=0, size of v2=3
------------------------------------
entering inside func1
Integer argument constructor called
destructor called
------------------------------------
entering inside func2
Integer argument constructor called
size of ptr2-vector =6
123456
------------------------------------
entering inside func3
Integer argument constructor called
size of v3=7
v3=1234567
------------------------------------
copy const called
v3=1004567
v4=1234567
------------------------------------
entering inside func4
Integer argument constructor called
v5=00000000
destructor called
------------------------------------
end of main
destructor called
destructor called
destructor called
destructor called
destructor called

*/
}

When is copy constructor called?

  • In C++, a Copy Constructor may be called in following cases:
    • When an object of the class is returned by value.
    • When an object of the class is passed (to a function) by value as an argument.
    • When an object is constructed based on another object of the same class.
    • When compiler generates a temporary object.
  • It is however, not guaranteed that a copy constructor will be called in all these cases, because the C++ Standard allows the compiler to optimize the copy away in certain cases, one example being the return value optimization 🍀 (sometimes referred to as RVO).
  • Best note of constructor. http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html

Return Value Optimization

Return value optimization, simply RVO, is a compiler optimization technique that allows the compiler to construct the return value of a function at the call site. The technique is also named "elision". C++98/03 standard doesn’t require the compiler to provide RVO optimization, but most popular C++ compilers contain this optimization technique, such as IBM XL C++ compiler, GCC and Clang . This optimization technique is included in the C++11 standard due to its prevalence. As defined in Section 12.8 in the C++11 standard, the name of the technique is "copy elision". MORE When the price of coping is high, RVO enables us to run the program much faster.

Virtual Destructors

  • A destructor can be declared to be virtual, and usually should be for a class with a virtual function.
class Shape {
  public:
    // ...
    virtual void draw() = 0;
    virtual ˜Shape();
};
class Circle {
  public:
    // ...
    void draw();
    ˜Circle(); // overrides ˜Shape()
    // ...
};
  • The reason we need a virtual destructor is that an object usually manipulated through the interface provided by a base class is often also deleted through that interface like
void user(Shape∗ p)
{
  p−>draw(); // invoke the appropriate draw()
  // ...
  delete p; // invoke the appropriate destructor
}
  • Had Shape’s destructor not been virtual that delete would have failed to invoke the appropriate derived class destructor (e.g., ˜Circle()). That failure would cause the resources owned by the deleted object (if any) to be leaked.

Constructor and member initializer list

  • In the definition of a constructor of a class, member initializer list specifies the initializers for direct and virtual base subobjects and non-static data members
  • The body of a function definition of any constructor, before the opening brace of the compound statement, may include the member initializer list, whose syntax is the colon character :, followed by the comma-separated list of one or more member-initializers

Initialization order

  • If the constructor is for the most-derived class, virtual base classes are initialized in the order in which they appear in depth-first left-to-right traversal of the base class declarations (left-to-right refers to the appearance in base-specifier lists)
  • Then, direct base classes are initialized in left-to-right order as they appear in this class's base-specifier list
  • Then, non-static data members are initialized in order of declaration in the class definition.
  • Finally, the body of the constructor is executed

when do we use Initializer list in C++

  • There are situations where initialization of data members inside constructor doesn’t work and Initializer List must be used. Following are such cases

    • For initialization of non-static const data members. const data members must be initialized using Initializer List. Reason for initializing the const data member in initializer list is because no memory is allocated separately for const data member, it is folded in the symbol table due to which we need to initialize it in the initializer list
    #include<iostream>
    using namespace std;
    
    class Test {
        const int t;
    public:
        Test(int t):t(t) {}  //Initializer list must be used
        int getT() { return t; }
    };
    
    int main() {
        Test t1(10);
        cout<<t1.getT();
        return 0;
    }
    
    /* OUTPUT:
       10  
    */
    • For initialization of reference members:
    // Initialization of reference data members
    #include<iostream>
    using namespace std;
    
    class Test {
        int &t;
    public:
        Test(int &t):t(t) {}  //Initializer list must be used
        int getT() { return t; }
    };
    
    int main() {
        int x = 20;
        Test t1(x);
        cout<<t1.getT()<<endl;
        x = 30;
        cout<<t1.getT()<<endl;
        return 0;
    }
    /* OUTPUT:
        20
        30
     */
    • For initialization of member objects which do not have default constructor:
    #include <iostream>
    using namespace std;
    
    class A {
        int i;
    public:
        A(int );
    };
    
    A::A(int arg) {
        i = arg;
        cout << "A's Constructor called: Value of i: " << i << endl;
    }
    
    // Class B contains object of A
    class B {
        A a;
    public:
        B(int );
    };
    
    B::B(int x):a(x) {  //Initializer list must be used
        cout << "B's Constructor called";
    }
    
    int main() {
        B obj(10);
        return 0;
    }
    /* OUTPUT:
        A's Constructor called: Value of i: 10
        B's Constructor called
    */
    • For initialization of base class members
    #include <iostream>
    using namespace std;
    
    class A {
      int i;
    public:
      A(int );
    };
    
    A::A(int arg) {
      i = arg;
      cout << "A's Constructor called: Value of i: " << i << endl;
    }
    
    // Class B is derived from A
    class B: A {
    public:
      B(int );
    };
    
    B::B(int x):A(x) { //Initializer list must be used
      cout << "B's Constructor called";
    }
    
    int main() {
      B obj(10);
      return 0;
    }
    
⚠️ **GitHub.com Fallback** ⚠️