.. highlight:: c++ :linenothreshold: 5 **************************** 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. :: // Operations you can perform without defining any methods // works with classes containing no dynamic data // all instance variables can be duplicated using simple assignment class Example { string name; int age; double grades[4]; public: string getName() {return name;} void setName(string name) {this->name = name;} int getAge() {return age;} void setAge(int age) {this->age = age;} double getGrade(int index) {return grades[index];} void setGrade(int index, int value) {grades[index] = value;} }; int main() { // allowed without explicit declaration Example exp; // trigger default constructor exp.setName("John"); exp.setAge(14); Example exp1(exp); // trigger copy constructor Example exp2; exp2 = exp; // trigger copy assignment operator overload // both exp1 and exp2 will have name as "John" and age as 14 } Dynamic data in class ===================== + pointer-typed instance variables * holding a single value/object or an array * allocated using ``new`` and data stored in heap * simple 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:: // declaration void function(MyClass myObj); // call MyClass myObj1; function(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 type ``MyClass &`` .. 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. :: class MyClass { private: int *arr; int size; public: MyClass(); MyClass(int size); MyClass(const MyClass& other); MyClass & operator=(const MyClass& other); ~MyClass(); } MyClass::MyClass() { size = 0; arr = nullptr; } MyClass::MyClass(int size) { this->size = size; arr = new int[size]; } MyClass::MyClass(const MyClass& other) { // you can access private members of other directly in C++! size = other.size; arr = new int[size]; for (int i = 0; i < size; ++i) arr[i] = other.arr[i]; } MyClass & MyClass::operator=(const MyClass& other) { size = other.size; // must release memory of the old dynamic data first! delete [] arr; arr = new int[size]; for (int i=0; iconfig = config; } }; int main() { Configuration *config1 = new Configuration(); // main function owns it MyClass obj1(config1); // obj1 uses it MyClass obj2(config1); // obj2 uses it delete config1; return 0; } The dynamic data can also be passed as a returned value. Example: The object always owns the data. The recipient just used it. :: class MyClass { private: int * array; // always owned by the object int size; public: // ... other methods // array is new'ed in some methods ~MyClass() {delete [] array;} int *getArray() {return array;} // expose to outside int getSize() {return size;} }; int main() { // declare and initialize data in MyClass obj1; int size = obj1.getSize(); int *arr = obj1.getArray(); for (int i = 0; i < size; ++i) cout << arr[i] << " "; return 0; } Example: The ownership is not clear. You can either let the object or the main function to delete the dynamic data. Both are reasonable. :: class MyClass { private: int * array; // always owned by the object int size; public: // ... other methods // array is new'ed in some methods int *getPositiveValues(); int getPositiveCount(); }; int * MyClass::getPositiveValues() { int posCount = getPositiveCount(); int *arr = new int[posCount]; // dynamic data, not meant to be owned int j = 0; for (int i = 0; i < size; ++i) if (array[i] > 0) { arr[j] = array[i]; ++j; } return arr; } int MyClass::getPositiveCount() { int count = 0; for (int i = 0; i < size; ++i) if (array[i] > 0) ++count; return count; } int main() { // declare and initialize data in MyClass obj1; int *positiveArray = obj1.getPositiveValues(); int size = obj1.getPositiveCount(); for (int i = 0; i < size; ++i) cout << positiveArray[i] << " "; delete [] positiveArray; // positiveArray owned by the main function return 0; }