6

I have this below program, where I am passing a vector by reference to a function myFunc and inside this function, I am adding few elements to the vector.

I am not freeing object creating with new, for now ignore the memory leak due to this.

After myFunc() execution is complete I am printing the variables ctor and dtor to know how many times constructor and destructor were called.

Output is:

Before Exiting 5 7

I am creating 5 objects so ctor is 5. But why is dtor 7? From where do the extra two counts? Am I missing something?

#include
#include
using namespace std;

static int ctor = 0;
static int dtor = 0;

class MyClass
{
public:
    MyClass(int n)
    {
        i = n;
        ctor++;
        // cout << "Myclass ctor " << ctor << endl; 
    } 

    ~MyClass()
    {
        dtor++; 
        // cout << "Myclass dtor" << dtor << endl;
    }

private: 
    int i;
}; 

void myFunc(vector<MyClass> &m);

void myFunc(vector<MyClass> &m)
{
    MyClass *mc;

    for(int i = 0; i < 5; i++)
    {
        mc = new MyClass(i);
        m.push_back(*mc);
    }
}

int main()
{

    vector<MyClass> m;
    vector<MyClass>::iterator it;

    myFunc(m);

    cout << "Before Exiting " << ctor << " " << dtor << endl;
}
Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
irappa
  • 749
  • 4
  • 11

5 Answers5

8

Vectors copy around objects, but only your int constructor increments ctor. That's not accounting for copy constructed objects, and because you didn't provide one, the compiler provided it for you.

Add

MyClass(const MyClass& rhs) i(rhs.i) { ++ctor; }

to your class to see if that balances the count.

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
2

Vectors start at a low size. When you push an element into them, they make a copy of it using the copy constructor so you don't see your normal constructor get called. When the vector size grows beyond its limit, it will increase its limit by a multiple of its current size (e.g. double it).

Vectors are guaranteed to always keep objects in contiguous memory, so if adding a new object exceeds the vectors capacity() (i.e. size() + 1 > capacity()), the vector allocates new memory somewhere and copies all elements into it. This will again use the copy constructor. So, your elements from the vector pre-resize will call their destructors after they are copied into the newly allocated space with their copy-constructor.

So, more destructor calls than normal constructor calls :)

John Humphreys
  • 37,047
  • 37
  • 155
  • 255
  • I understand it now. Though I was aware of copy contructor, I did not think of copy operation performed internally by vector. Thanks. – irappa Sep 02 '11 at 19:21
1

Vectors sometimes call another constructor, copy constructor, which was implicitly generated by the compiler for your class. That's why some ctor++ calls are missing: not all objects were constructed with the constructor you defined, some were constructed with the other one.

To ensure correct behavior of a type (class) with vector, you must implement copy constructor for it:

MyClass(const MyClass& rhs) { i = rhs.i; ++ctor; } // copy constructor

...because the one generated by compiler does nothing.

hamstergene
  • 24,039
  • 5
  • 57
  • 72
1

As indicated by others, the reason behind your result is the copy constructor and resizing of the vector. A vector has both a size and a capacity. The capacity is normally always doubled when the vector has to resize to accommodate new elements so that resizing does not have to happen all that often.

Adding some trace code to print out the vector capacity between each push_back gives more clarity into this behaviour.

m.capacity(): 0
m.capacity(): 1
m.capacity(): 2
m.capacity(): 4
m.capacity(): 4
m.capacity(): 8
Before Exiting 5 7

What actually happens here is that the only time the destructor is called is when the vector is resized (see why below). The first time it's resized it has no elements, thus the destructor is never called. The second time, the capacity is 1 so the destructor is called once. The third time it's called twice and the fourth time it's called four times. This totals seven times called, just as the counter shows.

The elements dynamically allocated in myFunc are never deallocated, so the destructor never runs there, and the final printout ("Before Exiting...") is done before leaving the scope in which the vector is allocated, so the destructor for the last "vector reincarnation" isn't called until after that printout. Therefore, the destructor of MyClass is only called when the vector is resized.

Mikael Auno
  • 8,990
  • 2
  • 21
  • 16
  • Another trivial question. This is what I observed after adding few more printf statements. I printed the Object address returned by new operator, during creation of the objects. Later after calling myFunc(), I iterated through the vector and again printed the address of each object. I found that address now are different. It may be because of destruction and construction of objects while vector was getting re-sized ? Since push_back method takes object reference as parameter is it correct that, that object in caller got changed (interms of memory location) after the method returns ? – irappa Sep 03 '11 at 13:21
  • @irappa There are two reasons for new addresses. First of all, the `vector` holds actual objects as opposed to pointers to the objects passed to `push_back`. When when `push_back(foo)` is called the contents in `foo` are copied to another instance (of whatever class `foo` is of) inside the `vector`. Also, when a `vector` is resized the backing array is most often (always?) also moved to an new location, giving the elements contained within the `vector` new addresses. – Mikael Auno Sep 03 '11 at 15:10
0

§ 12.8.8 of the C++ standard says: If the class definition does not explicitly declare a copy constructor, there is no user-declared move constructor, and there is no user-declared move assignment operator, a copy constructor is implicitly declared as defaulted (8.4.2). Such an implicit declaration is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

Basically, since your struct violates the rule of five, the compiler made a copy constructor and assignment operator for you. That other constructor doesn't increment ctor, but uses the destructor you defined. The vector is then using that alternate constructor, as a speed improvement.

If you add protected: MyClass(const MyClass& b); to the class declaration, this problem goes away.

Community
  • 1
  • 1
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Actually, that last bit with the user-destructor seems to imply the compiler is breaking the rules. Hmm. Still, that line should fix the problem. – Mooing Duck Sep 02 '11 at 19:07