0

Below is the signature of vector pushback method in C++.

 void push_back (const value_type& val);

Now below is code

class MyInt
{
   int *a;
public:
  MyInt(int n):a(new int(n)){cout<<"constructor called"<<endl;}
   ~MyInt(){cout<<"destructor called"<<endl;}
   void show(){
   cout<<*a<<endl;
   }
 };


 void vector_test(vector<MyInt> &v)
 {
    MyInt m1(1);
    MyInt m2(2);
    v.push_back(m1);   
    v.push_back(m2);
 }



Output
-------------
constructor called
constructor called
destructor called
destructor called
destructor called
1
2
destructor called
destructor called

Here we see that for 2 objects which is created in vector_test function causes 2 times constructor invocation. But for destructor it got invoked 5 times.

Now my doubts and questions are

  1. How come number of constructor and destructor calls are not matching?
  2. I understand that there are some temporary objects getting created. But how this mechanism is working?
  3. Just to test, I tried to provide a copy constructor but it causes compilation failure. What could be the reason?
  4. Is there some logic inside which is copying the contents of m1 and m2 into new objects and puts in to the container?

I would really appreciate if someone explain this in detail. Thanks..

milleniumbug
  • 15,379
  • 3
  • 47
  • 71
Neeraj Kumar
  • 836
  • 1
  • 10
  • 29
  • 12
    You missed to track the copy constructor (there's one generated by the compiler). – πάντα ῥεῖ May 06 '16 at 17:16
  • And once you get the copy constructor written and tracked, note that the number of times the object is created and destroyed is dependent on the compiler, compiler optimizations, etc. – PaulMcKenzie May 06 '16 at 17:22
  • " I tried to provide a copy constructor but it causes compilation failure. What could be the reason?" show and maybe we can tell. Otherwise best anyone can say is you probably have a syntax error. – user4581301 May 06 '16 at 17:22
  • Yes you missed to track the copy constructor. What pei said. – maxadorable May 06 '16 at 17:22
  • Please add the copy constructor, there are meany reasons the compilation might fail. Also it would be nice to free the memory, other might need it later. Or even better don't use pointers. – Mircea Ispas May 06 '16 at 17:22
  • @Neeraj So you couldn't get the copy constructor corrected, you knew that it was necessary, but still posted your findings?? Does that make sense to you? – PaulMcKenzie May 06 '16 at 17:23
  • Putting line numbers in your code makes it harder for people to cut and paste what you've written, which in turn makes it less likely that folks will help you out. It doesn't matter here, because for experienced programmers the answer is obvious (we've all run into it in the past), but in general, don't do it. – Pete Becker May 06 '16 at 17:24
  • Possible duplicate / closely related Q from a while ago: [std::vector memory handling](http://stackoverflow.com/q/36568377/3425536) – Emil Laine May 06 '16 at 17:42
  • Thanks everyone for your response. – Neeraj Kumar May 06 '16 at 17:43
  • @user4581301: My mistake. Yes there was silly syntax error in copy constructor. I corrected it. Thank you. – Neeraj Kumar May 06 '16 at 17:44
  • @PeteBecker: Thank you for your suggestion. – Neeraj Kumar May 06 '16 at 17:44
  • @PaulMcKenzie: Thank you for your help. Could you please provide more information on temporary object creation? Actually I am looking out for possible sequence of events when temporary object is created and how copy constructor helps in these situations. – Neeraj Kumar May 06 '16 at 17:44
  • Don't forgot all the compiler generated constructors. To add real tracking you need to compensate for the copy and move constructors. – Martin York May 06 '16 at 17:56
  • useful `g++` options for the sake of observing: `-fno-elide-constructors` – Han XIAO Jul 10 '18 at 13:34

2 Answers2

1

You need to add logging also for copy constructor:

MyInt(const MyInt& rhs):a(new int(*rhs.a)){cout<<"copy constructor called"<<endl;}

Some constructor calls are allowed by compiler to be elided, ie. in following line:

MyInt m1 = 1;

you might expect copy constructor to be first called to instantiate temporary MyInt(1) then a copy constructor called with this temporary. So you would see:

constructor called // for temporary
copy constructor called // for m1
destructor called       // for m1
destructor called  // for temporary

but because of copy elision compiler will directly instantiate your m1 instance using MyInt(int n) constructor, even though your copy constructor has side effects (std::cout is used). So no above // for temporary logs will be present.

To see this with gcc use: -fno-elide-constructors option which diasbles copy constructor elision.

Also it is a good practice to make constructors such as MyInt(int n) explicit, this is to disallow making MyInt object instance by mistake - you will have to make it explicit, i.e. MyInt var; var = static_cast<MyInt>(1);.

marcinj
  • 48,511
  • 9
  • 79
  • 100
0

You have to remember that the compiler can potentially add five methods if you don't provide them. Im this case you are being affected by the compiler generated copy constructor.

#include <iostream>
#include <vector>
using namespace std;

class MyInt
{
   int *a;
public:
  MyInt(int n):a(new int(n)){cout<<"constructor called"<<endl;}
  MyInt(MyInt const& copy): a(new int(*copy.a)) {cout<<"copy constructor called"<<endl;}
  MyInt(MyInt&& move): a(move.a) {move.a = nullptr; cout<<"move constructor called"<<endl;}
   ~MyInt(){cout<<"destructor called"<<endl;}
   void show(){
   cout<<*a<<endl;
   }
 };


 void vector_test(vector<MyInt> &v)
 {
    MyInt m1(1);
    MyInt m2(2);
    v.push_back(m1);
    v.push_back(m2);
 }

 int main()
 {
    vector<MyInt> v;
    vector_test(v);
 }

When I run this I get

batman> ./a.out
constructor called
constructor called
copy constructor called
copy constructor called
copy constructor called
destructor called
destructor called
destructor called
destructor called
destructor called

Note: You are leaking memory from the destructor.

This is also why we have the emplace_back() interface. This will reduce the number of copies of the object that are created. Additionally when optimizations are enabled you will see that some of those object are not copied but created in-place.

 void vector_test(vector<MyInt> &v)
 {
    MyInt m1(1);
    MyInt m2(2);

    // Can use emplace back
    v.emplace_back(1); // Created the object in-place in the vector.

    // Or you can use a temorary.
    v.push_back(2);  // Compiler sees there is a single argument constructor
                     // and will insert the constructor to convert a 2 into
                     // MyInt object.

    v.push_back(MyInt(3)); // Or you can create a temporary explicitly.
 }
Martin York
  • 257,169
  • 86
  • 333
  • 562