So, given that a class will have a pointer to memory that is allocated at run time, what member methods and/or operators will be needed to be written (as a minimum for the thing to work) and why? |
This is a loaded question and it is dependent on the situation, but I think your friend is hinting at the following necessities:
1. destructor (virtual if a base class)
2. copy constructor
3. overloaded assignment
Let's say we have the following classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
//myClass.h
#include <exception>
using namespace std;
class unallocatedException: public exception
{
virtual const char* what() const throw()
{
return "Pointer is unallocated";
}
} myUnallocatedException;
class myClass
{
private:
int* intPtr;
public:
myClass()
{
intPtr = 0; //null ptr
}
void setInt(int n)
{
if(intPtr)
{
delete intPtr;
intPtr = 0;
}
intPtr = new int;
*intPtr = n;
}
int getInt()
{
if(!intPtr)
{
throw myUnallocatedException;
}
return *intPtr;
}
};
|
and we do the following:
1 2 3 4 5 6 7 8 9 10
|
#include "myClass.h"
int main()
{
{
myClass c;
c.setInt(5);
}
return 0;
}
|
there will be a
memory leak after c is destroyed (it got an int pointer from the heap (4 bytes) but it never deallocated it). A
destructor is needed to avoid memory leaks when an instance of the class is destructed. In this case, we need the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
class myClass
{
private:
...
public:
...
~myClass()
{
if(intPtr)
{
delete intPtr;
}
}
};
|
If this class will be derived at some point, the destructor should be declared
virtual so that the destructors are called in the correct order.
Now, let's say we did the following:
1 2 3 4 5 6 7 8 9 10 11 12
|
int main()
{
myClass c1;
myClass c2;
c1.setInt(10);
c2.setInt(20);
c1 = c2;
return 0;
}
|
This program results in undefined behavior since the assignment results in a
shallow copy of intPtr (not to mention a memory leak as access to c1's intPtr is gone forever). Now, when c1 goes out of scope,
c2's intPtr would be deleted. Then, when c2 goes out of scope, its intPtr would be deleted again. The same thing would happen if c2 went out of scope first (which I think would happen in the above example). To avoid double deletion we could set intPtr to zero in the destructor but there would still be a memory leak.
To fix this, we need to
overload the assignment operator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
class myClass
{
private:
...
public:
...
myClass& operator=(const myClass& other)
{
if(intPtr)
{
delete intPtr;
}
intPtr = new int;
*intPtr = *(other.intPtr);
return *this;
}
~myClass()
{
if(intPtr)
{
delete intPtr;
}
}
};
|
There is still one more problem. Say we do this in the code:
1 2 3 4 5 6 7 8 9
|
int main()
{
myClass c1;
c1.setInt(10);
myClass c2 = c1;
return 0;
}
|
In the above example, the overloaded assignment operator isn't called, but rather
c2's copy constructor is called. C++ gives all objects a default copy constructor, and guess what, its default behavior is to do a simple assignment of all the member variables. This results in a shallow copy of intPtr. A memory leak occurs similar to the above.
To fix this, we give myClass a copy constructor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
class myClass
{
private:
...
public:
...
myClass(const myClass& other)
{
intPtr = new int;
*intPtr = *(other.intPtr);
}
myClass& operator=(const myClass& other)
{
if(intPtr)
{
delete intPtr;
}
intPtr = new int;
*intPtr = *(other.intPtr);
return *this;
}
~myClass()
{
if(intPtr)
{
delete intPtr;
}
}
};
|
Now, we can assign and copy construct instances of myClass and have them go out of scope without memory leaks!
How do I create a function in C++ that will have default values for parameters? |
In the declaration of the function, do something like this:
int myFunc(int a=5, bool b=false);
This says that a call to myFunc, if given no parameters, would result in a being given the default value of 5 and b being given the default value of false. If in a call to myFunc, a is specified but b is not, b would be given the default value of false and a would be given whatever is passed in.
The definition of the function would look just like all other functions:
1 2 3 4
|
int myFunc(int a, bool b)
{
...
}
|