Skip to content

containter

krishna edited this page Apr 18, 2021 · 33 revisions

contents

what is a container?

  • A container is an object holding a collection of elements

  • We need a mechanism to ensure that the memory allocated by the constructor is deallocated; that mechanism is a destructor

    class Vector {
      private:
        double∗ elem; // elem points to an array of sz doubles
        int sz;
      public:
      Vector(int s) :elem{new double[s]}, sz{s} // constructor: acquire resources
      {
        for (int i=0; i!=s; ++i) // initialize elements
        elem[i]=0;
      }
      ˜Vector() { delete[] elem; } // destructor: release resources
      double& operator[](int i);
      int size() const;
    };
  • The name of a destructor is the complement operator, ˜, followed by the name of the class; it is the complement of a constructor.

  • Vector’s constructor allocates some memory on the free store (also called the heap or dynamic store) using the new operator. The destructor cleans up by freeing that memory using the delete operator.

  • This is all done without intervention by users of Vector. The users simply create and use Vectors much as they would variables of built-in types. For example,

    void fct(int n)
    {
      Vector v(n);
      // ... use v ...
      {
        Vector v2(2n);
        // ... use v and v2 ...
      } // v2 is destroyed here
      // ... use v ..
    } // v is destroyed here
  • Vector obeys the same rules for naming, scope, allocation, lifetime, etc as does a built-in type, such as int and char.

  • consider a graphical illustration of a vector pointer

  • The constructor allocates the elements and initializes the Vector members appropriately. The destructor deallocates the elements.

  • The technique of acquiring resources in a constructor and releasing them in a destructor, known as Resource Acquisition Is Initialization or RAII, allows us to eliminate ‘‘naked new operations,’’ that is, to avoid allocations in general code and keep them buried inside the implementation of well-behaved abstractions

  • Similarly, ‘‘naked delete operations’’ should be avoided. Avoiding naked new and naked delete makes code far less error-prone and far easier to keep free of resource leaks

Initializing Containers 👊

  • A container exists to hold elements, so obviously we need convenient ways of getting elements into a container.
  • we can initialize a container using the following two methods and are declared as below
    • Initializer-list constructor: Initialize with a list of elements.
    • push_back(): Add a new element at the end (at the back of) the sequence.
    class Vector {
      public:
        Vector(std::initializer_list<double>); // initialize with a list of doubles
        // ...
        void push_back(double); // add element at end, increasing the size by one
        // ...
    };
  • The std::initializer_list used to define the initializer-list constructor is a standard-library type known to the compiler: when we use a {}-list, such as {1,2,3,4}, the compiler will create an object of type initializer_list to give to the program. So we can Write
    Vector v1 = {1,2,3,4,5}; // v1 has 5 elements
    Vector v2 = {1.23, 3.45, 6.7, 8}; // v2 has 4 elements
  • Vector’s initializer-list constructor might be defined like this:
    Vector::Vector(std::initializer_list<double> lst) // initialize with a list
    :elem{new double[lst.size()]}, sz{static_cast<int>(lst.size())}
    {
      copy(lst.begin(),lst.end(),elem); // copy from lst into elem
    }

Abstract types

  • Types such as Vector are called concrete types because their representation is part of their definition. In that, they resemble built-in types.

  • In contrast, an abstract type is a type that completely insulates a user from implementation details. 💝

  • First, we define the interface of a class Container which we will design as a more abstract version of our Vector:

    class Container {
      public:
      virtual double& operator[](int) = 0; // pure virtual function
      virtual int size() const = 0; // const member function
      virtual ˜Container() {} // destr uctor
    };
  • This class is a pure interface to specific containers defined later.

  • The word virtual means ‘‘may be redefined later in a class derived from this one.’’ 🌺 💥 🔥

  • Unsurprisingly, a function declared virtual is called a virtual function. 👊

  • A class derived from Container provides an implementation for the Container interface.

  • The curious =0 syntax says the function is pure virtual;🍁 that is, some class derived from Container must define the function.:fire: :hibiscus: :punch:

  • Thus, it is not possible to define an object that is just a Container; a Container can only serve as the interface to a class that implements its operator and size() functions.

  • A class with a pure virtual function 💞 is called an abstract class. 🌺🔥💝💑

  • This Container can be used like this:

    void use(Container& c)
    {
      const int sz = c.size();
      for (int i=0; i!=sz; ++i)
        cout << c[i] << '\n';
    }
  • Note how use() uses the Container interface in complete ignorance of implementation details. It uses size() and [] without any idea of exactly which type provides their implementation.

  • A class that provides the interface to a variety of other classes is often called a polymorphic type.🌺

  • As is common for abstract classes, Container does not have a constructor🍁. After all, it does not have any data to initialize.

  • On the other hand, Container does have a destructor 🌿 🌹 and that destructor is virtual.🌷 Again, that is common for abstract classes because they tend to be manipulated through references or pointers, and someone destroying a Container through a pointer has no idea what resources are owned by its implementation🍀

  • A container that implements the functions required by the interface defined by the abstract class Container could use the concrete class Vector:

    class Vector_container : public Container { // Vector_container implements Container
      Vector v;
      public:
      Vector_container(int s) : v(s) { } // Vector of s elements
      ˜Vector_container() {}
      double& operator[](int i) { return v[i]; }
      int size() const { return v.size(); }
    };
  • The :public can be read as ‘‘is derived from’’ or ‘‘is a subtype of.’’ Class Vector_container is said to be derived from class Container, and class Container is said to be a base of class Vector_container. 🌹

  • An alternative terminology calls Vector_container and Container subclass and superclass, respectively.

  • The derived class is said to inherit members from its base class, so the use of base and derived classes is commonly referred to as inheritance.🍀

  • The members operator[]() and size() are said to override the corresponding members in the base class Container.

  • The destructor (˜Vector_container()) overrides the base class destructor (˜Container()).

  • Note that the member destructor (˜Vector()) is implicitly invoked by its class’s destructor (˜Vector_container()).

  • For a function like use(Container&) to use a Container in complete ignorance of implementation details, some other function will have to make an object on which it can operate. For example

    void g()
    {
      Vector_container vc {10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
      use(vc);
    }
  • Since use() doesn’t know about Vector_containers but only knows the Container interface, it will work just as well for a different implementation of a Container. For example:

    class List_container : public Container { // List_container implements Container
      std::list<double> ld; // (standard-librar y) list of doubles
      public:
        List_container() { } // empty List
        List_container(initializer_list<double> il) : ld{il} { }
        ˜List_container() {}
        double& operator[](int i);
        int size() const { return ld.size(); }
      };
      double& List_container::operator[](int i)
      {
        for (auto& x : ld) {
          if (i==0) return x;
          −−i;
        }
        throw out_of_range("List container");
    }
  • Here, the representation is a standard-library list<double>. Usually, I would not implement a container with a subscript operation using a list, because performance of list subscripting is atrocious compared to vector subscripting. However, here I just wanted to show an implementation that is radically different from the usual one.

  • A function can create a List_container and have use() use it:

    void h()
    {
      List_container lc = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
      use(lc);
    }
  • The point is that use(Container&) has no idea if its argument is a Vector_container, a List_container, or some other kind of container; it doesn’t need to know. It can use any kind of Container. It knows only the interface defined by Container. Consequently, use(Container&) needn’t be recompiled if the implementation of List_container changes or a brand-new class derived from Container is used.:rose:

  • The flip side of this flexibility is that objects must be manipulated through pointers or references:herb:

Virtual Functions

  • Consider again the use of Container:
void use(Container& c)
{
  const int sz = c.size();
  for (int i=0; i!=sz; ++i)
    cout << c[i] << '\n';
}
  • How is the call c[i] in use() resolved to the right operator[]()? 🌺 🔥 ❤️ 🍀 When h() calls use(), List_container’s operator[]() must be called. When g() calls use(), Vector_container’s operator[]() must be called.:punch:
  • To achieve this resolution, a Container object must contain information to allow it to select the right function to call at run time. 🔥
  • The usual implementation technique is for the compiler to convert the name of a virtual function into an index into a table of pointers to functions. That table is usually called the virtual function table or simply the vtbl🌿.
  • Each class with virtual functions has its own vtbl identifying its virtual functions. This can be represented graphically like this: pointer
  • The functions in the vtbl allow the object to be used correctly even when the size of the object and the layout of its data are unknown to the caller.
  • The implementation of the caller needs only to know the location of the pointer to the vtbl in a Container and the index used for each virtual function.
  • This virtual call mechanism can be made almost as efficient as the ‘‘normal function call’’ mechanism (within 25%). Its space overhead is one pointer in each object of a class with virtual functions plus one vtbl for each such class.
  • Virtual functions are called according to the type of object pointed or referred, not according to the type of pointer or reference.:hibiscus::four_leaf_clover::fire: In other words, virtual functions are resolved late, at runtime. Virtual keyword is used to make a function virtual
  • If we are using virtual function in the context of inheritance, followings are necessary
    • A base class and a derived class.
    • A function with same name in base class and derived class.
    • A pointer or reference of base class type pointing or referring to an object of derived class

Class Hierarchies

pointer

  • The arrows represent inheritance relationships. For example, class Circle is derived from class Shape.
    class Shape {
      public:
        virtual Point center() const =0; // pure virtual
        virtual void move(Point to) =0;
        virtual void draw() const = 0; // draw on current "Canvas"
        virtual void rotate(int angle) = 0;
        virtual ˜Shape() {} // destructor
      // ...
    };
  • Naturally, this interface is an abstract class: as far as representation is concerned, nothing (except the location of the pointer to the vtbl) is common for every Shape. Given this definition, we can write general functions manipulating vectors of pointers to shapes:
      void rotate_all(vector<Shape∗>& v, int angle) // rotate v’s elements by angle degrees
      {
      for (auto p : v)
        p−>rotate(angle);
      }
  • To define a particular shape, we must say that it is a Shape and specify its particular properties (including its virtual functions):
    class Circle : public Shape {
      public:
        Circle(Point p, int rr); // constructor
        Point center() const { return x; }
        void move(Point to) { x=to; }
        void draw() const;
        void rotate(int) {} // nice simple algorithm
      private:
        Point x; // center
        int r; // radius
    };
  • So far, the Shape and Circle example provides nothing new compared to the Container and Vector_container example, but we can build further:
    class Smiley : public Circle { // use the circle as the base for a face
      public:
        Smiley(Point p, int r) : Circle{p,r}, mouth{nullptr} { }
        ˜Smiley()
        {
          delete mouth;
          for (auto p : eyes)
          delete p;
        }
        void move(Point to);
        void draw() const;
        void rotate(int);
        void add_eye(Shape∗ s) { eyes.push_back(s); }
        void set_mouth(Shape∗ s);
        virtual void wink(int i); // wink eye number i
      // ...
      private:
        vector<Shape∗> eyes; // usually two eyes
        Shape∗ mouth;
    };
  • The push_back() member function adds its argument to the vector (here, eyes), increasing that vector’s size by one. (read more about vector later)
  • We can now define Smiley::draw() using calls to Smiley’s base and member draw()s:
    void Smiley::draw()
    {
      Circle::draw();
      for (auto p : eyes)
        p−>draw();
      mouth−>draw();
      }
  • Note the way that Smiley keeps its eyes in a standard-library vector and deletes them in its destructor. Shape’s destructor is virtual and Smiley’s destructor overrides it.
  • Why do we need to have a virtual Destructor for an Abstract Class? What happens if we don't have virtual destructor here?
  • A virtual destructor is essential for an abstract class because an object of a derived class is usually manipulated through the interface provided by its abstract base class. 🍀 💞🌿🔥🌹In particular, it may be deleted through a pointer to a base class.. Then, the virtual function call mechanism ensures that the proper destructor is called. 🍁 That destructor then implicitly invokes the destructors of its bases and members.
  • Virtual destructor is even more important for an interface. Any user of your class will probably hold a pointer to the interface, not a pointer to the concrete implementation. When they come to delete it, if the destructor is non-virtual, they will call the interface's destructor (or the compiler-provided default, if you didn't specify one), not the derived class's destructor. Instant memory leak. 👊 🔥
  • Object can be created by two ways as below
    • ClassName obj; --> here the obj is the real obj of type ClassName and its not a pointers
    • ClassName *ptr = new ClassName(); --> when new keyword is used, it will give you the pointer to the obj
    • BaseClass *bptr = new DerivedClass(); --> here bptr is a pointer of type BaseClass pointing to the object of type DerivedClass; It can't be otherway round
    • when you declare the pointer of an obj only like Derived *ptr;--> this will not invoke either constructor or destructor
    • when you assign pointer to a class using new, like Derived *ptr = new Derived(); then constructor call will get invoked
    • In Derived *ptr = new Derived();, when ptr gets out of scope destructor will not be invoked
      • If you have reserved some memory in the constructor here, and since constructor is called during Derived *ptr = new Derived();, we are not able to free the memory since destructor is not called when ptr goes out of scope. Isn't this the problem???. Soln is use delete ptr; in the same scope before ptr goes out of scope. When you do delete ptr, then the destructor will get invoked.
    • when you declare an object like Derived obj;. The default constructor will be called and also, after the obj is out of scope, destructor will be called.
    // If the Destructor in the base class is not virtual
    // the derived class's destructor will not be called when you
    // have base call type obj pointer pointing to base-calls type obj
    #include <iostream>
    #include <string>
    using namespace std;
    
    class Interface
    {
      public:
        virtual void doSomething()=0;
       virtual  ~Interface(){};
    };
    
    class Derived : public Interface
    {
       public:
            Derived(){
                cout<<"Dervired class constrcutor called\n"<<endl;
            }
           ~Derived()
           {
              cout<<"destructor called from the derived class\n"<<endl;
           }
           void doSomething(){
               cout<<"I am not doing anything\n"<<endl;
           }
    };
    
    void myFunc(void)
    {
       Interface *ptr = new Derived();
       delete ptr;
    }
    
    int main()
    {
      myFunc();
      cout <<"end of main"<<endl;
    }
    /*
    
    Dervired class constrcutor called                                                                                                                                                                                                                                                                                                                   
    destructor called from the derived class                                                                                                                                           
    end of main
      */

Explicit Overriding

  • A function in a derived class overrides a virtual function in a base class if that function has exactly the same name and type.

  • In large hierachies, it is not always obvious if overriding was intended. A function with a slightly different name or a slightly different type may be intended to override or it may be intended to be a separate function.

  • To avoid confusion in such cases, a programmer can explicitly state that a function is meant to override.

  • For example, I could (equivalently) have defined Smiley like this:

    class Smiley : public Circle { // use the circle as the base for a face
      public:
        Smiley(Point p, int r) : Circle{p,r}, mouth{nullptr} { }
        ˜Smiley()
        {
          delete mouth;
          for (auto p : eyes)
            delete p;
        }
        void move(Point to) override;
        void draw() const override;
        void rotate(int) override;
        void add_eye(Shape∗ s) { eyes.push_back(s); }
        void set_mouth(Shape∗ s);
        vir tual void wink(int i); // wink eye number i
        // ...
      private:
      vector<Shape∗> eyes; // usually two eyes
      Shape∗ mouth;
    };
  • Now, had I mistyped move as mve, I would have gotten an error because no base of Smiley has a virtual function called mve. Similarly, had I added override to the declaration of wink(), I would have gotten an error message.

  • What is this pointer?
    To understand this pointer, it is important to know how objects look at functions and data members of a class 👊 👊

    • Each object gets its own copy of the data member.
    • All-access the same function definition as present in the code segment Meaning each object gets its own copy of data members and all objects share a single copy of member functions.

Then now question is that if only one copy of each member function exists and is used by multiple objects, how are the proper data members are accessed and updated?

  • The compiler supplies an implicit pointer along with the names of the functions as ‘this’

The this pointer is passed as a hidden argument to all nonstatic member function calls and is available as a local variable within the body of all nonstatic functions. this pointer is a constant pointer that holds the memory address of the current object. this pointer is not available in static member functions as static member functions can be called without any object (with class name).

  • Note that this pointer is a r-value.

  • C++ lets object destroy themselves by calling the following code

    delete this;

Uses of this pointer

  1. when local variable's name is same as member's name
  2. To return reference to the calling object
Test& Test::func()
{
  // some processing
  return *this;
}

Static Members of a class

  • We can define class members static using static keyword
  • When we declare a member of a class as static it means no matter how many objects of the class are created, there is only one copy of the static member
  • A static member is shared by all objects of the class.
  • All static data is initialized to zero when the first object is created, if no other initialization is present
  • We can't put it in the class definition but it can be initialized outside the class as done in the following example by redeclaring the static variable, using the scope resolution operator :: to identify which class it belongs to.
#include <iostream>

using namespace std;

class Box {
   public:
      static int objectCount;

      // Constructor definition
      Box(double l = 2.0, double b = 2.0, double h = 2.0) {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;

         // Increase every time object is created
         objectCount++;
      }
      double Volume() {
         return length * breadth * height;
      }

   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void) {
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects.
   cout << "Total objects: " << Box::objectCount << endl;

   return 0;
}
/*
Constructor called.
Constructor called.
Total objects: 2
*/

Static Function Members

  • By declaring a function member as static, you make it independent of any particular object of the class 👊
  • A static member function can be called even if no objects of the class exist and the static functions are accessed using only the class name and the scope resolution operator ::
  • A static member function can only access static data member, other static member functions and any other functions from outside the class.
  • Static member functions have a class scope and they do not have access to the this pointer of the class. 🌺🌿
#include <iostream>

using namespace std;

class Box {
   public:
      static int objectCount;

      // Constructor definition
      Box(double l = 2.0, double b = 2.0, double h = 2.0) {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;

         // Increase every time object is created
         objectCount++;
      }
      double Volume() {
         return length * breadth * height;
      }
      static int getCount() {
         return objectCount;
      }

   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

// Initialize static member of class Box
int Box::objectCount = 0;

int main(void) {
   // Print total number of objects before creating object.
   cout << "Inital Stage Count: " << Box::getCount() << endl;

   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2

   // Print total number of objects after creating object.
   cout << "Final Stage Count: " << Box::getCount() << endl;

   return 0;
 }
   /*
    Inital Stage Count: 0
    Constructor called.
    Constructor called.
    Final Stage Count: 2
*/

Class objects as static

  • Just like variables, objects also when declared as static have a scope till the lifetime of program.
  • why is the output different for the two programs below:
// CPP program to illustrate
// when not using static keyword
#include<iostream>
using namespace std;

class GfG
{
    int i;
    public:
        GfG()
        {
            i = 0;
            cout << "Inside Constructor\n";
        }
        ~GfG()
        {
            cout << "Inside Destructor\n";
        }
};

int main()
{
    int x = 0;
    if (x==0)
    {
        GfG obj;
    }
    cout << "End of main\n";
}

/*
Inside Constructor
Inside Destructor
End of main
*/
// CPP program to illustrate
// class objects as static
#include<iostream>
using namespace std;

class GfG
{
    int i = 0;

    public:
    GfG()
    {
        i = 0;
        cout << "Inside Constructor\n";
    }

    ~GfG()
    {
        cout << "Inside Destructor\n";
    }
};

int main()
{
    int x = 0;
    if (x==0)
    {
        static GfG obj;
    }
    cout << "End of main\n";
}
/*
Inside Constructor
End of main
Inside Destructor
*/

Const function

  • A const member function, identified by a const keyword at the end of the member function's header 🌹, cannot modifies any data member of this object🌺. For example,
class Circle {
private:
   double radius;                 // Member variable called "radius"
   ......
public:
   void setRadius(double radius) { // Function's argument also called "radius"
      this->radius = radius;
         // "this.radius" refers to this instance's member variable
         // "radius" resolved to the function's argument.
   }
   double getRadius() const {  // const member function
   radius = 0;  
      // error: assignment of data-member 'Circle::radius' in read-only object
   return radius;
  }
};
  • There are two things to remember about the const function 👊:🔥
    • It will view the Obj as read only 💞. So you can't change any members of that object through that functions. It will protect from unintentionally changing any of the member variables ( see the above error)
    • When a function is declared as const, it can be called on any type of object (i.e both const object and non-const obj)👊💝
  • Remmember-: Non-const functions can only be called by non-const objects🔥👊🌹 (you can't call non-const function by a const obj)
//Demonstration of const member function--part1
#include<iostream>
using namespace std;

class Point
{
    public:
        Point(double x, double y)
        {
            cout << "constructor called\n"<<endl;
            this->x = x;
            this->y = y;
        }
        double sum ()const
        {
           // x = 3;  //16:15: error: assignment of member 'Point::x' in read-only object
            return x+y;
        }

        ~Point()
        {
            cout<<"destructor called \n"<<endl;
        }

    private:
        double x;
        double y;
};

int main()
{
Point P1(2,4);
const Point P2(2.3, 3.0);
cout << P1.sum()<< "\n"<<endl;
cout<<P2.sum();
cout <<"\nend of main\n";  
}
/*
constructor called
constructor called
6
5.3
end of main
destructor called
destructor called
*/

Lets suppose sum() function is non-const, then you can re-assign 3 to x(by mistake) and if you use const Obj to call this function, it will throw an error

//Demonstration of const member function-part2
#include<iostream>
using namespace std;

class Point
{
    public:
        Point(double x, double y)
        {
            cout << "constructor called\n"<<endl;
            this->x = x;
            this->y = y;
        }
        double sum ()
        {
            x = 3; //see you can change the member variable in this function if the
                   //func is not const
            return x+y;
        }

        ~Point()
        {
            cout<<"destructor called \n"<<endl;
        }

    private:
        double x;
        double y;
};

int main()
{
Point P1(2,4);
//const Point P2(2.3, 3.0); //you can't call a non-const function from a const object
Point P2(2.3, 3.0);
cout << P1.sum()<< "\n"<<endl;
cout<<P2.sum();
cout <<"\nend of main\n";  
}

Copy and Move

  • By default, objects can be copied. This is true for objects of user-defined types as well as for built-in types.
  • The default meaning of copy is member-wise copy: copy each member.
  • When we design a class, we must always consider if and how an object might be copied. For simple concrete types, member-wise copy is often exactly the right semantics for copy. For some sophisticated concrete types, such as Vector, member-wise copy is not the right semantics for copy, and for abstract types it almost never is.

Copying Containers

  • When a class is a resource handle, that is, it is responsible for an object accessed through a pointer, the default member-wise copy is typically a disaster.:punch:
  • Memberwise copy would violate the resource handle’s inv ariant
    void bad_copy(Vector v1)
    {
      Vector v2 = v1; // copy v1’s representation into v2
      v1[0] = 2; // v2[0] is now also 2!
      v2[1] = 3; // v1[1] is now also 3!
    }
    Assuming that v1 has four elements, the result can be represented graphically like this: pointer
  • We need to define better copy semantics here.
  • Copying of an object of a class is defined by two members: a copy constructor and a copy assignment 🌹
    class Vector {
      private:
        double∗ elem; // elem points to an array of sz doubles
        int sz;
      public:
        Vector(int s); // constructor: establish invariant, acquire resources
        ˜Vector() { delete[] elem; } // destructor: release resources
        Vector(const Vector& a); // copy constr uctor
        Vector& operator=(const Vector& a); // copy assignment
        double& operator[](int i);
        const double& operator[](int i) const;
        int size() const;
    };
  • A suitable definition of a copy constructor for Vector allocates the space for the required number of elements and then copies the elements into it, so that after a copy each Vector has its own copy of the elements:
Vector::Vector(const Vector& a) // copy constructor
  :elem{new double[sz]}, // allocate space for elements
  sz{a.sz}
{
  for (int i=0; i!=sz; ++i) // copy elements
    elem[i] = a.elem[i];
}
  • some open questions

    • what is the difference between copy-constructor and copy-assignment?
    • Difference Between & and &&
    • Difference Between new and malloc( )
    • Difference Between Recursion and Iteration
    • Difference Between Function Overloading and Overriding in C++
    • Difference Between Constructor and Destructor
    #include<iostream>
    #include<stdio.h>
    
    using namespace std;
    
    class Test
    {
    public:
       Test() { x = 0;}
       Test(const Test &t)
       {  //t.x = 10;  you cant do it.. t is read-only obj (benefit of const)
          // x = 10; you can do it. x is the member variable of this obj
          cout<<"Copy constructor called "<<endl;
       }
       Test& operator=(const Test &t)
       {
          cout<<"Assignment operator called "<<endl;
       }
       private:
        int x;
    };
    
    int main()
    {
      Test t1, t2; //calls the default constructor
      t2 = t1;  //calls the copy-assignment ( note no obj is created here)
      Test t3 = t1; //calls the copy-constructor.. new Obj t3 is created here
      Test t4(t2);  //calsl the copy-constructor.. new Obj t4 is created here
      return 0;
    }
    /*
    Assignment operator called
    Copy constructor called
    Copy constructor called
    */
  • From above example note that, Copy constructor is called when a new object is created from an existing object💖, as a copy of the existing object

  • assignment operator is called when an already initialized object is assigned a new value from another existing object.💞

Clone this wiki locally