Constructors and destructors are basic functions in any OO langauge, including C++, so we better get them right.
Consider:
class MyClass { public: }; int main() { MyClass My; }You may claim that nothing happens, but thats not true.
The MyClass object My i constructed; as we did not provide a constructor the compiler will create one for us. Then the object is destructed, and the destructor is called, again the compiler will create a destructor for us.
The next example show this:
class MyClass { public: MyClass() { std::cout << "Default Constructor" << std::endl; } ~MyClass() { std::cout << "Destructor" << std::endl; } } int main() { MyClass My; }A constructor is a function in a class (or struct) which is called when a object is created, you normally don't call it yourself, the compiler will do it for you. A constructor has the same name as the class.
A destructor is a function which is called when the object is destructed, again you don't call the destructor, it's done by the compiler. A destructor has the same name as the class, except that is preceeed by a ~
Neither constructors nor destructors can return values, you can't specify any return value
A Default constructor is a constructor which does not take any arguments.
You can create constructors which does take arguments:
MyClass(int i) : MyInt(i) { std::cout << "Int Constructor" << std::endl; }The ": MyInt(i)" syntax means that the member variable MyInt is initialized with the value of i. This syntax can only be used to initialize member varibles and base classes and only from constructors.
And to use it:
MyClass My(12); MyClass *My2 = new MyClass(12);The "int constructor" will in both cases be called with 12 as the argument.
Remember to delete My2 when you are done, this will call the destructor:
delete My2;But this constructor can also be used with:
MyClass My3 = 12;Again this will call the "int constructor", and nothing else. This in contrast to:
MyClass My3; My3 = 12;Which will call the default constructor for My3, then the "int constructor" to construct a MyClass object from 12 and finally the assignment operator.
If you don't create a assignment operator, the compiler will try to create one. This is in some cases fine, but you might want to create one:
MyClass &operator = (const MyClass &rhs) { std::cout << "Assignment operator" << std::endl; MyInt = rhs.MyInt; return *this; }The lession in this is; allways assign values to objects when they are created.
Now consider:
class MyClass { public: MyClass() { p = new int; *p = 0; } ~MyClass() { delete p; } };This seems fine, the constructor allocates space for the int, and the destructor deletes it.
However we end up in troubles, if you use it with:
MyClass M1; MyClass M2(M1);Why is that? Because M2 is created with a copy of M1, the copy constructor is called, as we did not create one, the compiler did it for us. But this copy constructor will let M2.p point at M1.p, so both the destructor for M1 and M2 will try to delete what it points at, but only one int has been allocated.
To solve this problem we must create a propper copy constructor:
MyClass(const MyClass &rhs) { p = new int; *p = *rhs.p; }Now everyone is happy? No, there is one more problem, if the class is used this way:
MyClass M3; M3 = M1;M3 will allocate space for the int (M3.p) but the assignment operator that the compiler creates as we has not created one will make M3.p point at M1.p, so the original M3.p is never deleted, and M1.p is deleted twice.
The fix is to create a proper assignment operator:
MyClass &operator = (const MyClass &rhs) { *p = *rhs.p; return *this; }One more issue; what will happen if the user writes:
MyClass M1; M1 = M1The default constructor will be called and then the assignment operator. This is fine in this trivial case, but if the class looks like this:
class MyClass { public: MyClass() { Size = 10; p = new int [Size]; } ~MyClass() { delete [] p; } MyClass &operator = (const MyClass &rhs) { delete [] p; p = new int [rhs.Size]; Size = rhs.Size; memcpy(p, rhs.p, Size*sizeof(int)); return *this; } int *p; int Size; };This is fine, if one does not try to assign an object to itself (M1 = M1), but if we do we will delete our own p, allocate space again, and copy uninitialized data from our own p to our own p, and thus forget the content of the original buffer.
To fix this we must check if rhs is ourself:
MyClass &operator = (const MyClass &rhs) { if(this != &rhs) { delete [] p; p = new int [rhs.Size]; Size = rhs.Size; memcpy(p, rhs.p, Size*sizeof(int)); } return *this; }The lesson is: If you have pointers in your class you should create a copy constructor and a assignment operator.
If the user is not ment to use these, you can add a private prototype for them and don't implement them. This will make the compiler complain if the user tries to use them:
private: MyClass &operator = (const MyClass &rhs); MyClas(const MyClass &rhs);