2

I'm sort of new to the technical reasons for constructors and destructors. I wrote a program with functions that display an objects starting state, end state, memory location, value, and when the constructors and destructors are called.

I'm having trouble understanding why they are called when they are and what they're actually doing. I'll post the results of a test run and the code I'm using - as well as what I know lol.


RESULTS:

Constructor called on 0x7fffc053f070

Initial state: 
Location: 0x7fffc053f070
  Value: 0

--- FOO ----
Location: 0x7fffc053f070
  Value: 1

--- BAR ----
Location: 0x7fffc053f080
  Value: 2

Destructor called on 0x7fffc053f080

--- BAZ ----
Location: 0x7fffc053f070
  Value: 2


Final state: 
Location: 0x7fffc053f070
  Value: 2

Destructor called on 0x7fffc053f070


CODE:

#include <iostream>
#include <vector>

using namespace std;

//Short memory addresses are on the heap
//Long memory addresses are on the stack

class A {

public:

A(){
    m_iValue = 0;
    cout << "Constructor called on " << this << endl;
}

/*      A(const A & a){
            m_iValue = a.m_iValue;
            cout << "Copy constructor called on " << this << endl;
    }
*/
void increment(){
    m_iValue++;
}

void display(){
    cout << "Location: " << this << endl;
    cout << "  Value: " <<m_iValue << endl;
    cout << endl;
}

virtual ~A(){
    cout << "Destructor called on " << this << endl;
}

private:
    int m_iValue;
};

void foo(A & a){
    a.increment();
    a.display();
}

void bar(A a){
    a.increment();
    a.display();
}

void baz(A * a){
    a->increment();
    a->display();
}

void blah(vector<A*> vA){
    vA.back()->display();
    delete vA.back();
    vA.pop_back();
}

int main(int argc, char * argv[]){

    A a;

    cout << "Initial state: " << endl;
    a.display();

    cout << endl;

    foo(a);
    bar(a);
    baz(&a);
    cout << endl;
    cout << "Final state: " << endl;
    a.display();

    return 0;
}

What I believe is happening:

So, the constructor is getting called once and the destructor is getting called twice. The constructor is called when the object is created in main. In foo(), m_iVariable is passed by reference, and the function increments m_iValue for the object at that location in memory. So the program displays the value as 1 (incremented from 0.)

This is where I get confused.. The third location is different from the first two locations. The object is getting passed in directly to bar(). I don't understand how the location can be different without calling the constructor or why the destructor is called after it increments, making the value 2.

But baz also increments the value. So that means bar didn't actually do anything? I still don't get how bar displays a new memory location and destructs, but never constructs.


Sorry for all the text, but anything will help. Thanks!

Oh and the commented out code, and the function blah were used for other things and aren't relative for this question.

ModdedLife
  • 669
  • 2
  • 11
  • 24

5 Answers5

3

When you pass by value to bar(), you are creating a new object, local to that function. That object is destroyed when bar() returns. It is initialised by the copy constructor A(A const &), which was implicitly generated since you don't declare one yourself. If you uncomment the copy constructor, then you will see it happening.

In general, you have to be careful when allowing objects with a non-trivial destructor to be copied. Such classes typically manage resources which are released in the destructor, and you must take care that copies don't try to manage the same resource. Always remember the Rule of Three when making classes like that.

Community
  • 1
  • 1
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Ok thanks, that makes sense. So since I have the copy constructor commented out, when bar() is called - it essentially does nothing? It creates an object as that function is called and deconstructs as the function exits? – ModdedLife Feb 28 '13 at 17:29
  • 1
    @ModdedLife: The implicit copy constructor initialises the object by copying each data member. So when `bar` is called, it creates a new object with a copy of the old object's counter; increments and displays the new object; then destroys the new object. Nothing happens to the old object. – Mike Seymour Feb 28 '13 at 17:35
2

In very general terms, a constructor is called when you either initialize an object implicitly by creating it on the stack as you do in your main routine, or when you explicitly allocate and initialize it using new. It's important to note that passing arguments to methods by value has the effect of creating a copy of an object using the copy constructor which is a type of initialization.

The destructor is called when an object allocated on the stack falls out of scope, or when a dynamically allocated object is released with delete.

You're seeing two destructor calls here because one of your methods is pass by value, and a copy of it is created, and, later when the method is complete, destroyed. The memory address of these two objects is different.

If you're passing by value, any modifications made to the copy will not be reflected in the original. This is why in a lot of C++ applications, methods pass things either by reference, like foo(A& a) to allow modifications, or by const reference to make it clear no changes are allowed like foo(const A& a). Pointers are sometimes used for the same purposes.

The reason you're having problems here is because you've not aware of the Rule of Three with regards to destructors, copy constructors, and copy assignment operators.

tadman
  • 208,517
  • 23
  • 234
  • 262
0

First, the reason that your output shows different number of constructor and destructor calls, is that it doesn't display all such calls. In your case it's a failure of your attempt to instrument the code, not considering all constructors. In general it can also be caused by constructors throwing exceptions, because it's only the number of successful constructor cakks that matches the number of destructor calls, and it can be caused by objects not being destroyed (e.g. dynamically allocated and not deleted).


Ignoring (ab)use of very low level features of the language, the C++ rules are designed to ensure that

  • For each object of type T, there is exactly one T constructor call, which happens before any other call.

  • For each constructor call that succeeds, there is a corresponding destructor call (if the object is ever destroyed).

These rules hold recursively in an object hierarhcy. An object with sub-objects (data members) is an example of such a hierarchy. Instead of being a data member, a sub-object can be a base class sub-object.

The constructor's responsibility is technically to transform a piece of raw memory into a typed and meaningful object (constrast this with a copy assignment operator, which replaces an existing value with a new one, possibly deallocating earlier allocated memory). At the design level the constructor's main responsibility is to establish the class invariant, whatever you can assume about the state of the object between calls to public methods. The destructor's job is to clean up, e.g. to free resources.

Construction failure is, from the language point of view, when a constructor throws an exception.

A new-expression provides a transactional-like very strong coupling between memory allocation and construction. It allows you to provide two set of arguments: a first set of arguments for the allocation function, and a second set of arguments for the constructor. Except for the case noted below, if the constructor fails then the memory is automatically deallocated and the exception is propagated. I.e. in general either both succeed, or any side-effects are undone and the calling code informed of the failure.

The single exception where the cleanup is not done, is where you have defined a custom allocation function, a so called "placement new" operator, and failed to provide a corresponding deallocation function. I do not know the reason why the custom deallocation function is needed, and indeed, this is the only circumstance where it's implicitly called.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
0

In your method bar(A a), it is the copy constructor A(const A& a) that is called. If you have not declare it, it is implicitely created by the compiler and calls the copy constructor for each member.

Just add this method:

A(const A& a){
  m_iValue = a.value;
  cout << "Copy constructor called on " << this << endl;
}
cedrou
  • 2,780
  • 1
  • 18
  • 23
0

Its always fun to just simply put print statements in your constructor and destructor to see them operate on run time. A lot of good answers above so I wont say anything, but doing this helped me early on in my programming career.

Shawn
  • 667
  • 8
  • 19