370 likes | 641 Views
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
E N D
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 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