Beginning C++ Through Game Programming, Second Edition by Michael Dawson Chapter 9 Advanced Classes and Dynamic Memory: Game Lobby Objectives • • • • Combine objects Use friend functions Overload operators Dynamically allocate and free memory • Avoid memory leaks • Produce deep copies of objects Aggregation • Game objects often composed of other objects – Drag racer: body, four tires, an engine • Other times, see an object as a collection of related objects – Zoo as a collection of animals • Can mimic these kinds of relationships among objects in OOP using aggregation Using Object Data Members • A data member that's an object itself • Has a relationship • A Critter object can have an attribute m_Name that is a string object Critter Class class Critter { public: Critter(const string& name = ""); string GetName() const; private: string m_Name; }; Critter::Critter(const string& name): m_Name(name) {} inline string Critter::GetName() const { return m_Name; } Container Data Members • You can also use containers as data members for your objects • A Farm object has a single data member that's a vector for Critter objects Farm Class class Farm { public: Farm(int spaces = 1); void Add(const Critter& aCritter); void RollCall() const; private: vector<Critter> m_Critters; }; Farm Class (cont.) Farm::Farm(int spaces) { m_Critters.reserve(spaces); } void Farm::Add(const Critter& aCritter) { m_Critters.push_back(aCritter); } void Farm::RollCall() const { for (vector<Critter>::const_iterator iter = m_Critters.begin(); iter != m_Critters.end(); ++iter) cout << iter->GetName() << " here.\n"; } Using Critter and Farm int main() { Critter crit("Poochie"); cout << "Critter's name:" << crit.GetName() cout << "\nCreating critter farm.\n"; Farm myFarm(3); cout << "\nAdding three critters to farm.\n"; myFarm.Add(Critter("Moe")); myFarm.Add(Critter("Larry")); myFarm.Add(Critter("Curly")); cout << "\nCalling Roll...\n"; myFarm.RollCall(); return 0; } Friend Functions • Friend functions have complete access to any member of a class • Specify function is friend of a class with friend before function prototype inside class definition • In a Critter class: friend void Peek(const Critter& aCritter); • Peek() can access any member of a Critter object • Outside of Critter class definition, define global function Peek(): void Peek(const Critter& aCritter) { cout << aCritter.m_Name << endl; } • Peek() can access private member m_Name of a Critter object Overloading Operators • • • • Give meaning to built-in operators used with new types that you define Overload the << operator so it can work with Critter objects Can send Critter object << to cout Outside a Critter class, define a global function that overloads << operator ostream& operator<<(ostream& os, const Critter& aCritter) { os << "Critter Object - "; os << "m_Name: " << aCritter.m_Name; return os; } • Function can directly access the private data member m_Name of a Critter object because function is a friend of the Critter class friend ostream& operator<<(ostream& os, const Critter& aCritter); Dynamically Allocating Memory • Local variables live on the stack • Local variables don't persist beyond function in which declared • Heap memory persists until programmer frees it • Dynamic heap memory offers efficiency – Use only memory needed, free when done – Access memory even after a function ends (without having to return a copy of the object) The new Operator • Allocates memory on the heap and returns its address • Use new followed by the type of value int* pHeap = new int; • new int allocates memory on the heap for one int and returns its address • int* pHeap, declares a local pointer, pHeap, which points to the newly allocated chunk of memory on the heap • Can initialize memory on the heap at the same time you allocate it int* pHeap = new int(10); Memory on the Heap Persists int* intOnHeap() { int* pTemp = new int(20); return pTemp; } • Can access heap memory from another function int* pHeap2 = intOnHeap(); The delete Operator • Memory on the heap must be explicitly freed delete pHeap; • Memory on heap pointed to by pHeap is returned to the heap for future use • Good rule of thumb—every new should have a corresponding delete delete Pitfalls • Freeing memory on the heap with delete does not alter pointers to the heap • When calling delete on a pointer, potential problem of dangling pointer • Never dereference dangling pointers • Can assign 0 to them, as in delete pHeap; pHeap = 0; • Or immediately assign dangling pointer valid memory address Memory Leak 1 • Allocate memory on heap but have no way to free • Large enough leak, a program runs out of memory and crashes void leak1() { int* drip1 = new int(30); } • No way to free the allocated memory on heap that stores 30 Memory Leak 1 (cont.) • To avoid – – delete drip1 in leak1() function OR return copy of drip1 and free memory later Memory Leak 2 void leak2() { int* drip2 = new int(50); drip2 = new int(100); delete drip2; } • Assigning drip2 address of memory on the heap that stores the 100 leaks memory on the heap that stores 50 Memory Leak 2 (cont.) • To avoid leak, delete drip2 before assigning new memory address to it Declaring Data Members on the Heap • Given the constructor of a Critter class Critter::Critter(const string& name, int age) { cout << "Constructor called\n"; m_pName = new string(name); m_Age = age; } • Each Critter object has data member, m_pName, that's a pointer to memory on the heap Destructors • Member function that’s called just before an object is destroyed • Most often used for cleanup • Must have the name of the class preceded by a tilde (~) character • No parameters and no return value • If you don’t write a destructor of your own, complier will supply a default destructor Destructor Example • If necessary to clean up memory on the heap—default constructor won't do ~Critter() { cout << "Destructor called.\n"; delete m_pName; } • When class allocates memory on the heap, write a destructor that frees that memory Copy Constructors • Sometimes an object is copied automatically • Copying is done by a special member function called the copy constructor • Occurs when an object is – – – – Passed by value to a function Returned from a function Initialized to another object through an initializer Provided as a single argument to the object’s constructor Default Copy Constructor • Default copy constructor is supplied if you don’t write your own • Copies the value of each data member to data members of the same name in the new object—shallow copy • For simple classes, default copy constructor is usually fine • For classes with a data member that points to value on the heap, usually write your own copy constructor Default Copy Constructor Pitfalls • Default copy constructor creates shallow copies • Potential problem if object has data member on the heap Default Copy Constructor Pitfalls (cont.) • Deleting shallow copy leaves original object with dangling pointer Copy Constructor • Copy constructor must have the same name as the class • Returns no value, but accepts a reference to an object of the class— the object that needs to be copied • Reference is almost always made a constant reference to protect the original object from being changed during the copy process Copy Constructor Example • Following allocates separate memory on the heap for the name of the new Critter object //copy constructor definition Critter::Critter(const Critter& c) { cout << "Copy Constructor called\n"; m_pName = new string(*(c.m_pName)); m_Age = c.m_Age; } Copy Constructor Example Continued • Copy constructor creates deep copy • When copy of object is destroyed, original is unaffected Overloading the Assignment Operator • When both sides of an assignment statement are objects of the same class, the class’ assignment operator member function is called • A default assignment operator member function is supplied for you if you don’t write one of your own • Default assignment operator provides only memberwise duplication • For simple classes, the default assignment operator is usually fine • When you have a class with a data member that points to a value on the heap, you usually provide an overloaded assignment operator of your own; if you don’t, you’ll end up with shallow copies of objects when assigning one object to another. Overloading the Assignment Operator Critter& Critter::operator=(const Critter& c) //overloaded assignment op def { cout << "Overloaded Assignment Operator\n"; if (this != &c) { delete m_pName; m_pName = new string(*(c.m_pName)); m_Age = c.m_Age; } return *this; } Summary • Aggregation is the combining of objects so that one is part of another • Friend functions have complete access to any member of a class • Operator overloading allows you to define new meanings for built-in operators as they relate to objects of your own classes • The stack is an area of memory that is automatically managed for you and is used for local variables • The heap is an area of memory that the programmer can use to allocate and free memory Summary (cont.) • The new operator allocates memory on the heap and returns its address • The delete operator frees memory on the heap that was previously allocated • A dangling pointer points to an invalid memory location • Dereferencing or deleting a dangling pointer can cause your program to crash • A memory leak is an error in which memory that has been allocated becomes inaccessible and can no longer be freed Summary (cont.) • A destructor is a member function that’s called just before an object is destroyed • If you don’t write a destructor of your own, the complier will supply a default destructor for you • The copy constructor is a member function that’s invoked when an automatic copy of an object is made • A default copy constructor is supplied for a class if you don’t write one of your own • The default copy constructor simply copies the value of each data member to data members with the same names in the copy, producing a shallow copy Summary (cont.) • A deep copy is a copy of an object that has no chunks of memory in common with the original • A default assignment operator member function, which produces a shallow copy, is supplied for you if you don’t write one of your own