Advanced Classes, Constructors, Destructors, and Copy Constructors

Classes are one of the most useful tools to organize your code. It can solve many problems, and if set up correctly can make large tasks such as game development extremely easy. Learning about the secrets of a class can solve many unwanted pointer bugs and the ability to distribute information throughout your program can be much easier. In this tutorial we'll discuss constructors, copy constructors, and destructors.

Class development

This will be our test class for this tutorial:

#ifndef __TEST_
#define __TEST_

class Test {
public:
        Test(int xy);
        Test();
        ~Test();
        Test(const Test& other);
        void Dummy(Test tes);
        void Dummy2(Test& tes);
        int getX();
private:
        int x;
};

#endif

The #ifndef __TEST_ is there because we want to check if this name has been defined yet, if it hasn't then we define the name __TEST_ and also define the class. This eliminates inclusion problems where you might include the same class twice. Now Test class will be defined once and subsequent definitions will not occur.

We declare a constructor Test(int xy), which is called an overloaded function of the default constructor which is Test(). This means we can call the constructor either with an integer or without one. ~Test() our destructor is also defined, it's important to put all your deletes and other clean-up code here. Test(const Test& other) is our copy constructor, it will copy the same class Test into this class, in case we have another Test class and we want this new class to copy the old one.

Dummy is a test function that allows our Test class to pass by value through the argument. Dummy2 is the same as Dummy except allows the Test class to pass by reference, this saves memory, instead of copying and deleting, we simply give the address of the memory where that class is located.

Our private section usually has our data variables, which we access using public methods. Outside this class nothing can access private section, protecting variables and eliminating unnecessary bugs where you may have name conflicts.

Implementation file of our class

#include <iostream>
#include "Test.h"

using namespace std;

Test::Test(int xy){
        cout << "Constructor\n";
        x = xy;
}
Test::Test(){
        cout << "Default Constructor\n";
        x = 1;
}
Test::Test(const Test& other){
        cout << "Copying Constructor\n";
        x = other.x;
}

Test::~Test(){
        cout << "Destructor\n";
}
void Test::Dummy(Test tes){
        cout << "d1: tes.x is: " << tes.x << endl;
}
void Test::Dummy2(Test &tes){
        cout << "d2: tes.x is: " << tes.x << endl;
}
int Test::getX(){
       cout << "Get X\n";
       return x;
}

int main(int argc, char**argv){
        cout << "welcome\n";
        Test t(5);
        cout << "t.x is: " << t.getX() << endl;
        Test *tee = new Test(t);
        cout << "tee->x is: " << tee->getX() << endl;
        Test *tee2;
        tee2 = new Test[5];
        tee2[1].Dummy(t);
        tee2[0].Dummy2(tee2[1]);
        delete tee;
        delete [] tee2;
        cout << "Finished exiting\n";
        return 0;
}

We have to define our class functions now using the :: scope operator to indicate what class our functions belong to. We also cout, what function we're in, so we can tell where we are and what gets called first.

Everything should be self-explanatory; however, the main function may confuse you.

We first declare a Test class named t, establish the x variable as 5. We establish this variable on the stack. Class Test variable tee and tee2 are established on the heap. The class tee is a single Test class that we establish using the copy-constructor so it is the same as "t", and contains 5 for x. The Test class tee2 is established on the heap and contains 5 classes, so the constructor will be called 5 times, it will call the default constructor, since you would need to loop through to establish a specific data for each constructor.

We then make some calls to Dummy, and then delete tee from the heap which calls the destructor. We also delete all 5 classes in tee2 using delete [] tee2. Then after we "finish exiting" the stack class t will also get deleted, and hence its destructor called.

If you run this code, you'll probably get a result similar to:
welcome
Constructor
Get X
t.x is: 5
Copying Constructor
Get X
tee->x is: 5
Default Constructor
Default Constructor
Default Constructor
Default Constructor
Default Constructor
Default Constructor
Default Constructor
d1: tes.x is: 5
Destructor
d2: tes.x is: 1
Destructor
Destructor
Destructor
Destructor
Destructor
Destructor
Destructor
Finished exiting
Destructor

In Dummy() you'll notice that the t in the stack gets deleted after a new default constructor is activated. This means that passing by value makes new copies and deletes later, which can mean more memory and less speed.
However in Dummy2() it doesn't get deleted or created, since its passed by reference.
Then we delete everything, then Finish exiting gets called, and finally the t on the stack gets deleted.

Conclusion

This should help you understand how constructors, copy constructors, and destructors work, if there are any questions please visit the forums and ask or leave comments. The copy constructor uses the least memory since it passed another class as const reference.

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.