Transcript Chapter 9

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 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::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 function prototype inside class definition before In a Critter class: friend void Peek(const Critter& aCritter); • • Peek() can access any member of a Outside of Critter function Peek() : Critter object class definition, define global void Peek(const Critter& aCritter) { cout << aCritter.m_Name << endl; } • Peek() Critter can access private member m_Name object of a

Overloading Operators

• • • • Give meaning to built-in operators used with new types that you define Overload the Can send << operator so it can work with Critter object << to cout Critter Outside a Critter overloads << class, define a global function that operator ostream& operator<<(ostream& os, const Critter& aCritter) { objects 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 returned to the heap for future use is • Good rule of thumb—every new have a corresponding delete should

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() OR return copy of drip1 function and free memory later

Memory Leak 2

void leak2() { int* drip2 = new int(50); drip2 = new int(100); delete drip2; } • Assigning drip2 stores the 100 address of memory on the heap that leaks memory on the heap that stores 50

Memory Leak 2 (cont.)

• To avoid leak, delete drip2 memory address to it before assigning new

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 member wise 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