Memory Management in Classes¶
When a class owns dynamic data (part of the data stored in heap memory region), special handling is required to avoid memory problems.
Implicitly-declared Methods¶
Some methods can be used without explicit declarations. They are usually methods that are essential to the object including the default constructor, destructor, copy constructor, copy assignment operator overloading, etc.
Note
You can safely rely on these implicitly-declared methods when all your instance variables are not dynamic (primitive, class-typed, non-dynamic array, etc.).
Default constructor
must declare and define explicitly if other constructor is present
use
MyClass::MyClass()=default;
if you have to define the default constructor but only the default behavior is desirable
Warning
Implicitly-declared default constructor will not set initial value to non-class-typed instance variables and the values are undefined.
Destructor
Copy constructor
Copy assignment operator overloading
Note
Any implicitly-declared method can only handle non-dynamic instance variables. They must be explicitly declared and defined when they are triggered in objects containing dynamic data.
1// Operations you can perform without defining any methods
2// works with classes containing no dynamic data
3// all instance variables can be duplicated using simple assignment
4class Example {
5 string name;
6 int age;
7 double grades[4];
8 public:
9 string getName() {return name;}
10 void setName(string name) {this->name = name;}
11 int getAge() {return age;}
12 void setAge(int age) {this->age = age;}
13 double getGrade(int index) {return grades[index];}
14 void setGrade(int index, int value) {grades[index] = value;}
15};
16
17int main() {
18 // allowed without explicit declaration
19 Example exp; // trigger default constructor
20 exp.setName("John");
21 exp.setAge(14);
22 Example exp1(exp); // trigger copy constructor
23 Example exp2;
24 exp2 = exp; // trigger copy assignment operator overload
25 // both exp1 and exp2 will have name as "John" and age as 14
26}
Dynamic data in class¶
pointer-typed instance variables
holding a single value/object or an array
allocated using
new
and data stored in heapsimple assignment will cause shallow copy
Potential memory problems
memory leak - fail to
delete
bad or no explicit destructor
dynamic data passed around without correct handling
shallow copy - fail to copy the dynamic data in the heap memory
only the memory addresses (values of pointers) are copied by assignment
implicitly-declared methods will cause shallow copy
bad or no explicit copy constructor
bad or no explicit copy assignment operator overload
only happens when objects are copied or copy assigned
will consequently cause the destructor to
delete
a same memory block in heap multiple times
Rule of three
The big three methods needed for classes to handle dynamic data correctly
destructor
mandatory because it will always be triggered
when an object is destroyed
local object going out of scope
dynamic object being
delete
d
place to
delete
dynamic data
copy constructor
optional not needed if never triggered
explicit triggering
MyClass obj2(obj1);
implicit triggering
MyClass obj2 = obj1;
implicit triggering in parameter passing:
1// declaration 2void function(MyClass myObj); 3 4// call 5MyClass myObj1; 6function(myObj1);
take a const reference parameter to avoid pass-by-value
perform deep copy here
copy assignment operator overload
optional not needed if never triggered
explicit triggering
MyClass obj2; obj2 = obj1;
implicit triggering Easy to miss
parameter passed by value
returned value
must clean up old data
take a const reference parameter to avoid pass-by-value
perform deep copy here
return *this;
to return a reference of its own class typeMyClass &
Note
Even in classes with dynamic data, the implementation of the big three is not always all mandatory. Only the destructor will be always triggered. Thus, the copy constructor or the copy assignment operator overloading implementations can be omitted if they are never triggered.
1class MyClass {
2 private:
3 int *arr;
4 int size;
5 public:
6 MyClass();
7 MyClass(int size);
8 MyClass(const MyClass& other);
9 MyClass & operator=(const MyClass& other);
10 ~MyClass();
11}
12
13MyClass::MyClass() {
14 size = 0;
15 arr = nullptr;
16}
17
18MyClass::MyClass(int size) {
19 this->size = size;
20 arr = new int[size];
21}
22
23MyClass::MyClass(const MyClass& other) {
24 // you can access private members of other directly in C++!
25 size = other.size;
26 arr = new int[size];
27 for (int i = 0; i < size; ++i)
28 arr[i] = other.arr[i];
29}
30
31MyClass & MyClass::operator=(const MyClass& other) {
32 size = other.size;
33 // must release memory of the old dynamic data first!
34 delete [] arr;
35 arr = new int[size];
36 for (int i=0; i<size; ++i)
37 arr[i] = other.arr[i];
38 return *this; // IMPORTANT! return the current object by reference
39}
40
41MyClass::~MyClass() {
42 delete [] arr;
43}
Rule of five (optional content)
Big three plus two more methods
Move constructor
Move assignment operator overloading
Not mandatory but will improve efficiency
New syntax since c++ 11 using
&&
to refer to a rvalue referenceMutable(non-const) parameter to allow modification
1// the parameter is no longer 'const' because you will modify it
2MyClass::MyClass(MyClass && other) {
3 size = other.size;
4 arr = other.arr;
5 // must make the other object ready for destruction
6 // without this step the destructor may try to delete the moved data
7 other.arr = null;
8 other.size = 0; // this is not necessary unless your destructor rely on the size
9}
10
11// the parameter is no longer 'const' because you will modify it
12MyClass & MyClass::operator=(MyClass && other) {
13 // Swap both the size and arr with the other object
14 // the destructor of the other object will destroy the old data
15 swap(size, other.size);
16 swap(arr, other.arr);
17 return *this;
18}
Ownership of Dynamic Data¶
Note
This section is for extended reading. It is beneficial to projects and self-improving but not essential in exams.
A dynamic data can be created anywhere and passed around. As the ownership changed. The last owner should take the responsibility to release the memory.
#. Passing dynamic data created outside as a parameter. The recipient owns the data after.
1class DataType {
2 // ...
3};
4
5class MyClass {
6 private:
7 DataType *myDynamicData;
8 public:
9 MyClass(DataType *initialData = nullptr) {
10 myDynamicData = initialData;
11 }
12 ~MyClass() {
13 // delete here although it is not new'ed in the class
14 delete myDynamicData;
15 }
16};
17
18int main() {
19 DataType *initData = new DataType(); // main function owns it
20 MyClass obj1(initData); // obj1 owns it now
21
22 return 0;
23}
#. You may also choose to remove the destructor and let the main function to delete if you feel it make more sense that the recipients are only users of the dynamic data and the main function is still the owner. Sometimes the dynamic data is shared in this case.
1class Configuration {
2 // ...
3};
4
5class MyClass {
6 private:
7 Configuration *config;
8 public:
9 MyClass(Configuration *config = nullptr) {
10 this->config = config;
11 }
12};
13
14int main() {
15
16 Configuration *config1 = new Configuration(); // main function owns it
17 MyClass obj1(config1); // obj1 uses it
18 MyClass obj2(config1); // obj2 uses it
19
20 delete config1;
21 return 0;
22}
The dynamic data can also be passed as a returned value.
Example: The object always owns the data. The recipient just used it.
1class MyClass {
2 private:
3 int * array; // always owned by the object
4 int size;
5 public:
6 // ... other methods
7 // array is new'ed in some methods
8 ~MyClass() {delete [] array;}
9 int *getArray() {return array;} // expose to outside
10 int getSize() {return size;}
11};
12
13int main() {
14 // declare and initialize data in MyClass obj1;
15 int size = obj1.getSize();
16 int *arr = obj1.getArray();
17 for (int i = 0; i < size; ++i)
18 cout << arr[i] << " ";
19 return 0;
20}
Example: The ownership is not clear. You can either let the object or the main function to delete the dynamic data. Both are reasonable.
1class MyClass {
2 private:
3 int * array; // always owned by the object
4 int size;
5 public:
6 // ... other methods
7 // array is new'ed in some methods
8 int *getPositiveValues();
9 int getPositiveCount();
10};
11
12int * MyClass::getPositiveValues() {
13 int posCount = getPositiveCount();
14 int *arr = new int[posCount]; // dynamic data, not meant to be owned
15 int j = 0;
16 for (int i = 0; i < size; ++i)
17 if (array[i] > 0) {
18 arr[j] = array[i];
19 ++j;
20 }
21 return arr;
22}
23
24int MyClass::getPositiveCount() {
25 int count = 0;
26 for (int i = 0; i < size; ++i)
27 if (array[i] > 0) ++count;
28 return count;
29}
30
31int main() {
32 // declare and initialize data in MyClass obj1;
33 int *positiveArray = obj1.getPositiveValues();
34 int size = obj1.getPositiveCount();
35 for (int i = 0; i < size; ++i)
36 cout << positiveArray[i] << " ";
37 delete [] positiveArray; // positiveArray owned by the main function
38 return 0;
39}