ppt

Implementing An OOD
(Chapters 3, 4)
Two Basic Steps
Convert the class diagram into C++ class
declarations (header file)
Convert the pseudocode into C++ methods
(code file)
Separation of files facilitates separate compilation,
covered later.
Elements of Good C++ Code
1) Readability
2) Consistent naming conventions
3) Code format
4) Comments
5) Language conventions
6) Coding style
Example: flush Program
Problem Statement:
write a program that represents a deck of cards,
simulates the dealing of poker hands, and
estimates the probability of dealing a flush, that is, a
hand with at least five cards of the same suit.
Sample Script
8% flush
Enter no. of cards in each hand (5-9): 7
Enter no. of hands to deal: 1000
DECK:
8D 1C 8H 5S 7C 2D 1H 10D 2S 6D 7S 2H 11H
10S 13C 11D 3C 3D 3H 9H 10C 2C 10H 7D 6H 12S
6C 3S 9D 1D 11S 5H 13D 7H 9S 4C 6S 8S 12D
5D 1S 9C 13H 5C 13S 4D 12C 8C 4S 11C 4H 12H
In 1000 7-card hands there were 35 flushes
9%
Chosen Object Classes
Simulation
Deck
Card
Pip
Suit
Simulation Class Aspects
contains a Deck object
manages the simulation of the dealing of multiple
hands
determines a frequency distribution of flushes
methods should be provided for:
creating a Simulation object
getting simulation parameters from user
managing the simulation, including memory
reporting results
Simulation Class Diagram
Simulation Class Implementation in
C++
class SimulationInfo {
private:
Deck the_deck;
CardArray one_hand;
IntArray suit_counts;
Integer
Integer
Integer
Integer
. . .
//
//
//
//
flush_count; //
num_hands;
//
//
hand_size;
//
hands_per_deck;//
//
The current deck
The current hand
Suit counts in current
hand
Total flush count
Total number of hands
to deal
Hand size (5-9 cards)
Number of hands that
can be dealt per deck
Simulation Class (cont'd)
. . .
public:
SimulationInfo();
void free();
void getdata();
//
//
//
//
//
//
//
//
//
void simulate();
void report();
private:
void loopThroughHands(); //
//
void dealAndCheckHand(); //
//
void initSuitCounts();
//
void incSuitCount();
//
//
void checkFlush();
//
//
//
};
Creates a new deck,
space for one hand,
and suit count array.
Returns memory created by
constructor.
Prompts for hand size and
number of hands.
Performs the dealing simulation.
Reports results.
Implements outer loop of
simulation.
Deals and checks one hand for
a flush.
Initializes suit counts to zero.
Increments suit count for each
card in hand.
Checks suit counts for values
>= 5 and increments flush
count if appropriate.
Elements of a Good C++ Class
Definition
Comments describe fields and methods when
appropriate
Blank lines promote readability
Public methods precede private methods
Method argument names are provided
Data names are private (or protected)
Methods not called externally are private
Class constructor and destructor are explicitly
defined (even though C++ can do it implicitly)
Relationship Between Types
Simulation and SimulationInfo
SimulationInfo is a C++ class type
Simulation is a C++ pointer type
Nearly every reference to a simulation will be via
the pointer type
One main exception: class construction
Why? Because C++ forces us to distinguish
between pointer and pointee
We must deal with pointers, and we must give
back the memory to which they point
Pointers and Objects
Unlike "pure" OOP languages like Java, Smalltalk,
and Eiffel, C++ distinguishes between objects and
pointers to them.
Thus there are two different syntaxes for method
invocation:
obj.method
objptr->method
if obj is a nonpointer
if objptr is a pointer
To avoid confusion and allow for dynamic object
creation, a consistent style of class definition is used.
Pointers and Objects (cont'd)
Use the pointer type Simulation when declaring:
- variables
- parameters
- data fields
Use the class type SimulationInfo to create an
object with new.
Once pointers exist, use the "->" form of reference
for both method and data access.
Finally, explicitly free all objects with delete.
Pointers and Objects (cont'd)
In general the actual class name, like
SimulationInfo, is only used with the new
operator. Exceptions:
When referencing or calling a static method
Example: StateInfo::blankUp
When small pieces of structured data (like a
graphics point) are copied over and over. These
should be coded as structs instead of classes.
When a class is meant to act as a primitive element
of the language (like a complex or rational number)
Using typedef for Clarity
We want to limit use of the C/C++ pointer token
(*) for both clarity and similarity to Java
We use typedef for this purpose:
typedef <existing type> <new type identifier>;
typedef
typedef
typedef
typedef
typedef
class
class
class
class
class
SimulationInfo * Simulation;
DeckInfo * Deck;
CardInfo * Card;
PipInfo * Pip;
SuitInfo * Suit;
If the typedef appears before the class definition,
the class modifier is required as a forward reference.
flush Main Program
main() {
Simulation s = new SimulationInfo;
s->getdata();
s->simulate();
s->report();
s->free();
}
Virtues of this Approach
Pointer references are much more efficient than
class references as:
class instance variables (construction time)
method parameters (call by value)
Enforces the discipline of dynamically creating
all objects from the heap, and returning objects to
the system when no longer needed.
Facilitates syntax so that all object data or
method references are made using
obj->data or obj->method
Naming Conventions in C++
Identifier Type
Type name
Method name
Field name (data)
Local variable
Constant
Examples
Integer
Simulation
State
solve
simulate
checkFlush
flush_count
problem
problem_
i
count
NUM_TILES
Standard Types
The basic type names of C++ do not conform to the
naming conventions. So use typedef to solve this:
typedef
typedef
typedef
typedef
typedef
typedef
typedef
typedef
int
Integer;
int *
IntArray;
char
Boolean;
char
Character;
double
Float;
char *
String;
String * StringArray;
Card *
CardArray;
File Structure
Class and method definitions should be systematically broken up into separate files, so that:
Readability is enhanced
Changes are localized
Program pieces can be separately compiled
C++ systems are composed of two types of files:
Header files: contain declarations only (.H type)
Code files (.cc or .C type)
Header Files
All header files have a common structure. Example:
//
//
//
//
//
//
****************************************
file:
SolverInfo.H
author:
T. Colburn
purpose: General problem solver header file
modified: 09/11/02
****************************************
#ifndef SOLVERINFO_USED
#define SOLVERINFO_USED
#include "ProblemInfo.H"
class SolverInfo {
. . .
};
#endif
Header File Structure
A block comment shows file name, description,
author, and any version or modification
information
The #ifndef-#define-#endif is a "guard"
against the file being included more than once
(see next slide)
Toward the top should be parameters (consts),
typedefs, and enumerations (in the example,
these are included from ProblemInfo.H)
After that, the declarations of the actual
classes
Multiply Defined Symbols
Foo.H
#include "Bar.H"
#include "Baz.H"
...
Bar.H
<symbols in here are
multiply defined>
Baz.H
#include "Bar.H"
...
Levels of Header Files
Unless the program is fairly trivial, there should
be a header file for each class:
SolverInfo.H
ProblemInfo.H
StateInfo.H
ActionInfo.H
ActionListInfo.H
In your lab exercise, you can have one header file
for all classes:
Flush.H
Code Files
Code files implement the methods declared in
header files.
There should be one code file for each class, e.g.
SolverInfo.cc, ProblemInfo.cc. etc.
Each code file should have a block comment at
the top, and #include its corresponding
header file.
Code files should have no local definitions, like
enumerations or non-class support functions
(should be private static methods instead).
Method bodies should be limited in size to what
can be displayed on one screen.
Code File Example
//
//
//
//
//
//
*****************************************
file: SolverInfo.cc
author: T. Colburn
purpose: Implements SolverInfo class methods
modified: 09/11/02
*****************************************
#include <iostream.h>
#include "SolverInfo.H"
SolverInfo::SolverInfo(Problem p) {
. . .
}
void SolverInfo::solve() {
. . .
}
String SolverInfo::promptActionName() {
. . .
}
Memory Management
Consider the SimulationInfo Constructor:
SimulationInfo::SimulationInfo() {
the_deck = new DeckInfo();
one_hand = new Card[9];
suit_counts = new Integer[4];
}
Note that memory is allocated dynamically, that is,
the DeckInfo object and supporting arrays are
allocated at run time.
Memory Management Problems
Array out of bounds
Dangling pointer references
Memory leaks
Memory management is the biggest problem when
using C++.
Solution: Plan memory management as part of the
design.
Array Out Of Bounds
Since arrays are represented simply as pointers to
memory, C++ does not check array bounds
Any array operation must have explicit bounds
checks put in by programmer
Whenever an array is passed, its size should also
be passed
Dangling Pointer References
ptr = new ClassInfo(. . .);
.
.
.
delete ptr;
.
.
.
<code that later uses
ptr again>
ptr
ptr
ClassInfo
Object
Who knows what
ptr is pointing to
now.
"Weird and wondrous" behavior ensues
Partial Solution: follow the delete with
ptr = NULL;
Memory Leaks
Suppose the following is in a loop:
ptr = new ClassInfo(. . .);
.
.
.
<loop ends without any delete>
ClassInfo
Object1
ClassInfo
Object2
ptr
ClassInfo
Object3
ClassInfo
Object4
ClassInfo
Object5
.
.
.
Preventing Memory Leaks
Use automatic garbage collection (not currently
available in C++)
Do reference counting. Each object keeps a
count of its users. When the count reaches
zero, the object is deleted.
Assign an owner to each object whenever
possible, and have the owner be responsible
for freeing memory (our approach).
Memory Management in the flush
Program
SimulationInfo
Object
creates
Deck Pointer,
DeckInfo Object
creates
Array of 9 Card
Pointers
creates
Array of 4 Integers
DeckInfo Class
class DeckInfo {
private:
CardArray cd_array;
// 52-element array to hold cards
public:
DeckInfo();
// Create the deck by allocating
// cd_array and creating a new
//
card for each element.
void free();
// Return memory created by
// constructor.
void shuffle();
// Shuffle deck by randomly
// swapping elements.
void deal(Integer, Integer,
// Deal cards into a hand.
CardArray);
void print();
// Print all cards in the deck.
};
DeckInfo Constructor
DeckInfo::DeckInfo() {
cd_array = new Card[52];
// Create new card array
for (Integer i = 0; i < 52 ; i++) // Fill array
cd_array[i] = new CardInfo(i); // with new cards
}
DeckInfo Object
creates
Array of 52 Card
Pointers,
52 CardInfo
Objects
CardInfo Class
class CardInfo {
private:
Integer deck_position;
//
Suit the_suit;
//
Pip the_pip;
//
public:
CardInfo(Integer n);
//
// pip depending on deck_position.
void free();
// its suit and pip objects.
Pip getPip();
This card's deck_position
This card's suit
This card's pip
Create a new card with suit and
// Return space for this card and
// Retrieve pip object for this
// card.
SuitInfo::type getSuitType(); // Retrieve suit type for this
// card.
void print();
};
// Print this card by printing pip
// value and suit.
CardInfo Constructor
CardInfo::CardInfo(Integer n)
{
deck_position = n;
the_suit = new SuitInfo(n);
the_pip = new PipInfo(n);
}
CardInfo Object
creates
Suit Pointer,
SuitInfo Object
Pip Pointer,
PipInfo Object
Summary of Memory Construction
1 SimulationInfo
1 Deck
1 DeckInfo
52 Cards,
52 CardInfos
52 Suits,
52 SuitInfos,
52 Pips,
52 PipInfos
9 Cards
4 Integers
Summary of Memory Construction
(cont'd)
One simulation creates a total of 328 pointers and
objects
If simulations were created over and over in a
loop (very common with simulations in general),
memory would be used up fast
This is a classic memory leak
Solution: make sure that when a simulation is
over, all memory created by it is returned
Recall flush Main Program
main() {
Simulation s = new SimulationInfo;
s->getdata();
s->simulate();
s->report();
s->free();
}
The free method must make sure that all memory
created by a simulation is returned (freed).
SimulationInfo::free()
Method
void SimulationInfo::free() {
the_deck->free();
// Return memory for all cards
delete [] one_hand;
// Return memory for hand array
delete [] suit_counts;
// Return memory for suit counts
// array
delete this;
}
one_hand and suit_counts, being arrays of
primitive objects (pointer and Integer), can be
returned using the C++ delete[] operator
The rest must be handled by the
DeckInfo::free() method
DeckInfo::free() Method
void DeckInfo::free() {
for (Integer i = 0; i < 52; i++) // Return memory
cd_array[i]->free();
// for each card
delete [] cd_array;
// Return memory
// for card array
delete this;
}
cd_array, being an array of pointers, can be
deleted
However, each CardInfo object must be handled
individually.
Note: cd_array must not be deleted first!
CardInfo::free() Method
void CardInfo::free() {
delete the_suit;
delete the_pip;
delete this;
}
A SuitInfo and a PipInfo object are deleted
Since the SuitInfo and PipInfo constructors
do not create any new objects, we are done.
Simulating a Dynamic
Multidimensional Array in C++
C++ only allows dynamic one-dimensional arrays.
However, some structures are best thought of in
terms of rows and columns.
For example, a 4x4 matrix of cards.
So we must use the row and column to convert into a
single dimension index.
Dynamic Multidimensional Arrays
(cont'd)
An abstract 2-D array like:
col
0
1
2
3
row
0 1 2 3
is represented in memory as:
0
10
row * 4 + col =
2 * 4 + 2 = 10
15
Dynamic Multidimensional Array
Example
CardArray cd_matrix;
Integer row, column;
Integer index;
row = 2;
column = 2;
cd_matrix = new CardInfo[4*4];
index = row*4+column;
cd_matrix[index] = new CardInfo(...);
Arrays of 3 dimensions and more must be handled
similarly.
Use of Constants
Using the preprocessor, as in:
#define
PI
3.14159
should be avoided in C++. Why? Strictly speaking,
the preprocessor is an extension to the language and
not part of the language itself.
Instead, use the const declaration:
const Integer PI = 3.14159;
const Integer NUM_TILES = 8;