The Structure of a C++ Program

Steven Zeil

Last modified: Dec 26, 2016
Contents:

1 Separate Compilation

1.1 Single File Programs

Putting your entire program into a single file is

1.2 Multiple File C++ Programs

By splitting a program up into multiple files that can be separately,

1.3 Separate Compilation

 

1.3.1 Object Code

1.3.2 Linking

 

Linking mainly consists of replacing symbols by real addresses.

On large projects with hundreds to thousands of files,

2 Pre-processing

The # Preprocessor

 

The preprocessor runs before the compiler proper.

The preprocessor:


Pre-Processor Instructions

The common pre-processor instructions are

2.1 #include

2.1.1 Example: #include (simple case)

g++ -E C.cpp > C.i

2.1.2 A more realistic example

In real programs, most of the code actually seen by the compiler may come from #includes

#include <iostream>

using namespace std;

int main() {
  cout << "Hello World" << endl;
  return 0;
}

Deja-Vu

 

This distinction will be important later.

2.2 Other Pre-processing Commands

#define

#define VersionNumber "1.0Beta1"

int main() {
   cout << "Running version "
        << VersionNumber
        << endl;

2.2.1 #ifdef, #ifndef, #endif

Used to select code based upon whether a macro has been defined:

#ifdef __GNUG__
  /* Compiler is gcc/g++ */
#endif
#ifdef _MSC_VER
  /* Compiler is Microsoft Visual C++ */
#endif


#if, #define, and #include

g++ -E C2.cpp > C2.i


Shorter Translation Unit

The result is file C2.i.

3 Declarations and Definitions

Common Errors


Declarations

A declaration in C++


Definitions

A definition in C++


General Rules for Decls & Defs

3.1 Decls&Defs: Variables

3.2 Decls&Defs: Functions

3.3 Decls&Defs: Data Types

4 Modules

Organizing Decls & Defs into Files


Headers and Compilation Units

A typical C++ program is divided into many source code files


Can You See Me Now?…Now?…Now?

How often does the compiler process each line of code from a file?

Therefore a header file can only contain things that can legally appear multiple times in a C++ program –


Division: headers and compilation units

4.1 Coupling and Cohesion


Coupling

![highCoupling][]

Something with high coupling has many dependencies on external entities

 

![lowCoupling][]

Something with low coupling has few dependencies on external entities

[highCoupling]: highCoupling.jpg “lots of wires” align=right

[lowCoupling]: lowCoupling.jpg “lots of wires” align=left


Cohesion

foldingKnife

s_screwdriver1

In something with high cohesion, all the pieces contribute to a well-defined, common goal.

 

swissArmy

In something with low cohesion, the pieces have little relation to one another


Extremely Low Cohesion

gig-swiss-knife


Dividing into modules

5 Example of Modularization: the auction program

Read this description of the auction program. It’s an example that we will use over and over throughout the semester.

The overall algorithm is pretty simple

main (fileNames[]){
  readItems;
  readBidders;
  readBids;
  for each item {
    resolveAuction (item, bidders, bids)
  }
}

We read in all the information about the auction, then resolve the bidding on each item, one at a time (in order by closing time of the bidding for the items).


The Auction Program before Modularization

We could implement that in one file:

auction1File.cpp
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

///////////////////////////////////////
// Basic Data Types
///////////////////////////////////////


/**
 * An amount of U.S. currency
 */
struct Money {
	int dollars;
	int cents; //< @invariant should be in the range 0..99, inclusive
};


/**
 * The time of the day, to the nearest second.
 */
struct Time {
  int hours;
  int minutes;
  int seconds;
};



/**
 * A Bid received during an auction
*/
struct Bid {
  std::string bidderName;
  Money amount;
  std::string itemName;
  Time bidPlacedAt;
};


/**
 * Someone registered to participate in an auction
 */
struct Bidder {
  std::string name;
  Money balance;
};


/**
 * An item up for auction
 */
struct Item {
	/// Name of the item
	std::string name;

	/// The minimum acceptable bid set by the seller
	Money reservedPrice;

	/// Time at which the auction ends
	Time auctionEndsAt;
};


/////////////////////////////////////
// Sequence Data Types
/////////////////////////////////////

/// A collection of bids
struct BidSequence {
	static const int capacity = 5000; ///< Max number allowed
	int size;                         ///< Number currently in array
	Bid data[capacity];               ///< The actual sequence data
};

/// A collection of bidders
struct BidderSequence {
	static const int capacity = 1000; ///< Max number allowed
	int size;                         ///< Number currently in array
	Bidder data[capacity];            ///< The actual sequence data
};

/// A collection of items
struct ItemSequence {
	static const int capacity = 500; ///< Max number allowed
	int size;                         ///< Number currently in array
	Item data[capacity];               ///< The actual sequence data
};


/////////////////////////////////////
// Global Variables
/////////////////////////////////////

/// The bidders participating in today's auction.
BidderSequence bidders;

/// The bids received for today's auction
BidSequence bids;

/**
 * The collection of all items being auctioned off for the day
 */
ItemSequence items;



/////////////////////////////////////
// Function Declarations
/////////////////////////////////////


/**
 * Adds two Money amounts together
 *
 * @param left  1st value to be added
 * @param right 2nd value to be added
 * @return sum of the two amounts
 */
Money add (const Money& left, const Money& right);


bool equal (const Money& left, const Money& right);


/**
 * Find the index of the bidder with the given name. If no such bidder exists,
 * return bidders.size.
 */
 int findBidder (std::string name, const BidderSequence& bidders);


 /**
 * Compare two Money amounts to see if the 1st is smaller
 * than the second
 *
 * @param left  1st value to be compared
 * @param right 2nd value to be compared
 * @return true iff left is a smaller amount than right
 */
bool lessThan (const Money& left, const Money& right);



/**
 *  Compare two times. Return true iff time1 is earlier than or equal to
 *  time2
 *
 *  Pre: Both times are normalized: sconds and minutes are in the range 0..59,
 *         hours are non-negative
 */
bool noLaterThan(const Time& time1, const Time& time2);


 /**
  * Print a monetary amount.The output format will always
  * include a decimal point and a two-digit cents amount.
  *
  * @param out the stream to which to print
  * @param money the value to be printed
  */
 void print (std::ostream& out, const Money& money);

 /**
  * Compare two Money amounts to see if they are equal
  *
  * @param left  1st value to be compared
  * @param right 2nd value to be compared
  * @return true iff the two amounts are equal
  */

 /**
  * Print a time in the format HH:MM:SS (two digits each)
  */
 void print (std::ostream& out, const Time& time);

 /**
  * Read a bid from the indicated file
  */
 void read (istream& in, Bid& bid);

 /**
  * Read all bids from the indicated file
  */
 void read (istream& in, BidSequence& bids);

 /**
 * Read a bidder from the indicated file
 */
void read (istream& in, Bidder& bidder);

/**
 * Read all bidders from the indicated file
 */
void read (istream& in, BidderSequence& bidders);


/**
 * Read one item from the indicated file
 */
void read (istream& in, Item& item);


/**
 * Read all items from the indicated file
 */
void read (istream& in, ItemSequence& items);


/**
 * Read a money value from the input.  Acceptable formats are
 *
 *        ddd.cc or ddd
 *
 * where ddd is any positive/negative integer of
 * one or more digits denoting dollars, and cc, if
 * supplied, is a two-digit integer.
 *
 * @param in   stream from which to read
 * @param money the value read in. Result is unpredictable if an I/O error occurs
 */
void read (std::istream& in, Money& money);

/**
 * Read a time (in the format HH:MM:SS) after skipping any
 * prior whitespace.
 */
void read (std::istream& in, Time& time);



/**
 * Determine the winner of the auction for item number i.
 * Announce the winner and remove money from winner's account.
 */
void resolveAuction (const BidSequence& bids, BidderSequence& bidders, const Item& item);


/**
 * Subtract one Money amount from another
 *
 * @param left  the minuend
 * @param right the subtrahend
 * @return difference of the two amounts
 */
Money subtract (const Money& left, const Money& right);






/////////////////////////////////////
// Function Bodies
/////////////////////////////////////



int main (int argc, char** argv)
{
  if (argc != 4)
    {
      cerr << "Usage: " << argv[0] << " itemsFile biddersFile bidsFile" << endl;
      return -1;
    }

  {
	  ifstream itemInput (argv[1]);
	  read (itemInput, items);
  }
  {
	  ifstream bidderInput (argv[2]);
	  read (bidderInput, bidders);
  }
  {
	  ifstream bidInput (argv[3]);
	  read (bidInput, bids);
  }


  for (int i = 0; i < items.size; ++i)
    {
      resolveAuction(bids, bidders, items.data[i]);
    }
  return 0;
}



/**
 * Adds two Money amounts together
 *
 * @param left  1st value to be added
 * @param right 2nd value to be added
 * @return sum of the two amounts
 */
Money add (const Money& left, const Money& right)
{
	Money result;
	result.dollars = left.dollars + right.dollars;
	result.cents = left.cents + right.cents;
	while (result.cents > 99)
	{
		result.cents -= 100;
		++result.dollars;
	}
	while (result.cents < 0)
	{
		result.cents += 100;
		--result.dollars;
	}
	return result;
}



/**
  * Compare two Money amounts to see if they are equal
  *
  * @param left  1st value to be compared
  * @param right 2nd value to be compared
  * @return true iff the two amounts are equal
  */
 bool equal (const Money& left, const Money& right)
 {
 	return (left.dollars == right.dollars)
 			&& (left.cents == right.cents);
 }



/**
 * Find the index of the bidder with the given name. If no such bidder exists,
 * return bidders.size.
 */
 int findBidder (std::string name, const BidderSequence& bidders)
 {
     int found = bidders.size;
     for (int i = 0; i < bidders.size && found == bidders.size; ++i)
     {
         if (name == bidders.data[i].name)
            found = i;
     }
     return found;
 }


  /**
  * Compare two Money amounts to see if the 1st is smaller
  * than the second
  *
  * @param left  1st value to be compared
  * @param right 2nd value to be compared
  * @return true iff left is a smaller amount than right
  */
 bool lessThan (const Money& left, const Money& right)
 {
 	if (left.dollars < right.dollars)
 		return true;
 	else if (left.dollars == right.dollars)
 		return left.cents < right.cents;
 	return false;
 }



 /**
  *  Compare two times. Return true iff time1 is earlier than or equal to
  *  time2
  *
  *  Pre: Both times are normalized: sconds and minutes are in the range 0..59,
  *         hours are non-negative
  */
 bool noLaterThan(const Time& time1, const Time& time2)
 {
     // First check the hours
     if (time1.hours > time2.hours)
         return false;
     if (time1.hours < time2.hours)
         return true;
     // If hours are the same, compare the minutes
     if (time1.minutes > time2.minutes)
         return false;
     if (time1.minutes < time2.minutes)
         return true;
     // If hours and minutes are the same, compare the seconds
     if (time1.seconds > time2.seconds)
         return false;
     return true;
 }


 /**
  * Print a monetary amount.The output format will always
  * include a decimal point and a two-digit cents amount.
  *
  * @param out the stream to which to print
  * @param money the value to be printed
  */
 void print (std::ostream& out, const Money& money){
 	out << money.dollars;
 	out << '.';
 	if (money.cents < 10)
 		out << '0';
 	out << money.cents;
 }


 /**
  * Print a time in the format HH:MM:SS (two digits each)
  */
 void print (std::ostream& out, Time& t)
 {
     if (t.hours < 10)
         out << '0';
     out << t.hours << ':';
     if (t.minutes < 10)
         out << '0';
     out << t.minutes << ':';
     if (t.seconds < 10)
         out << '0';
     out << t.seconds;
 }



 /**
  * Read a bid from the indicated file
  */
 void read (istream& in, Bid& bid)
 {
 	in >> bid.bidderName;
 	read (in, bid.amount);

 	read (in, bid.bidPlacedAt);

 	string word, line;
 	in >> word; // First word of itemName
 	getline (in, line); // rest of item name

 	bid.itemName = word + line;
 }


 /**
  * Read all bids from the indicated file
  */
 void read (istream& in, BidSequence& bids)
 {
     int nBids;
     in >> nBids;
     bids.size = nBids;
     for (int i = 0; i < nBids; ++i)
     {
         Bid bid;
         read (in, bid);
         bids.data[i] = bid;
     }
 }


 /**
 * Read a bidder from the indicated file
 */
void read (istream& in, Bidder& bidder)
{
	in >> bidder.name;
	read (in, bidder.balance);
}

/**
 * Read all bidders from the indicated file
 */
void read (istream& in, BidderSequence& bidders)
{
    int nBidders;
    in >> nBidders;
    bidders.size = nBidders;
    for (int i = 0; i < nBidders; ++i)
    {
    	read (in, bidders.data[i]);
    }
}


/**
 * Read one item from the indicated file
 */
void read (istream& in, Item& item)
{
  read (in, item.reservedPrice);
  read (in, item.auctionEndsAt);

  // Reading the item name.
  char c;
  in >> c; // Skips blanks and reads first character of the name
  string line;
  getline (in, line); // Read the rest of the line
  item.name = string(1,c) + line;
}


/**
 * Read all items from the indicated file
 */
void read (istream& in, ItemSequence& items)
{
  int nItems;
  in >> nItems;
  items.size = nItems;
  for (int i = 0; i < nItems; ++i)
    read (in, items.data[i]);
}


/**
 * Read a money value from the input.  Acceptable formats are
 *
 *        ddd.cc or ddd
 *
 * where ddd is any positive/negative integer of
 * one or more digits denoting dollars, and cc, if
 * supplied, is a two-digit integer.
 *
 * @param in   stream from which to read
 * @param money the value read in. Result is unpredictable if an I/O error occurs
 */
void read (std::istream& in, Money& money)
{
	if (!in) return;
	in >> money.dollars;
	if (!in) return;
    if (in.peek() == '.') // if next input is a '.'
    {
    	char decimal;
    	in >> decimal;
    	in >> money.cents;
    } else
    	money.cents = 0;
}




/**
 * Determine the winner of the auction for item number i.
 * Announce the winner and remove money from winner's account.
 */
void resolveAuction (const BidSequence& bids, BidderSequence& bidders, const Item& item)
{
  Money zero = {0, 0};
  Money highestBidSoFar = zero;
  string winningBidderSoFar;
  for (int bidNum = 0; bidNum < bids.size; ++bidNum)
    {
      Bid bid = bids.data[bidNum];
      if (noLaterThan(bid.bidPlacedAt, item.auctionEndsAt))
        {
          if (bid.itemName == item.name
            && lessThan(highestBidSoFar, bid.amount)
            && !lessThan(bid.amount, item.reservedPrice)
            )
            {
              int bidderNum = findBidder(bid.bidderName, bidders);
              Bidder bidder = bidders.data[bidderNum];
              // Can this bidder afford it?
              if (!lessThan(bidder.balance, bid.amount))
                {
                  highestBidSoFar = bid.amount;
                  winningBidderSoFar = bid.bidderName;
                }
            }
        }
    }

    // If highestBidSoFar is non-zero, we have a winner
    if (lessThan(zero, highestBidSoFar))
      {
        int bidderNum = findBidder(winningBidderSoFar, bidders);
        cout << item.name
             << " won by " << winningBidderSoFar
             << " for $";
        print (cout, highestBidSoFar);
        cout << endl;
        bidders.data[bidderNum].balance =
        		subtract (bidders.data[bidderNum].balance, highestBidSoFar);
      }
    else
      {
        cout << item.name
             << " reserve not met"
             << endl;
      }
}


/**
 * Read a time from the indicated stream after skipping any
 * leading whitespace
 */
void read (istream& in, Time& t)
{
  char c;
  in >> t.hours >> c >> t.minutes >> c >> t.seconds;
}



/**
 * Subtract one Money amount from another
 *
 * @param left  the minuend
 * @param right the subtrahend
 * @return difference of the two amounts
 */
Money subtract (const Money& left, const Money& right)
{
	Money result;
	result.dollars = left.dollars - right.dollars;
	result.cents = left.cents - right.cents;
	while (result.cents > 99)
	{
		result.cents -= 100;
		++result.dollars;
	}
	while (result.cents < 0)
	{
		result.cents += 100;
		--result.dollars;
	}
	return result;
}



… but it would not be a very good idea.

The details of the code for each function body really aren’t important right now (with a slight exception that I’ll get into later). The most important thing during modularization is to know what the functions and data are and what role they play in the program.


A Possible Modularization

From the description of the program and from a glance through the code, we might guess that the key modules would be

Items
Data and functions related to the items up for auction
Bidders
Data and functions related to the people bidding in the auction
Bids
Data and functions related to the bids placed by those people
Money
Data and functions related to money
Time
Data and functions related to time.

(In later lessons we’ll talk about other ways to identify good modules.)


Module Files

And we would then expect to divide the program into files corresponding to those modules:


**Making a List … **

If we then make a list of the data …

Types Data
Money bidders
Time bids
Bid items
Bidders
Item
BidSequence
BidderSequence
ItemSequence

**Making a List … **

… and of a list of the functions in this program …

Types Data Functions
Money bidders add
Time bids equal
Bid items findBidder
Bidders lessThan
Item noLaterThan
BidSequence print (money)
BidderSequence print (time)
ItemSequence read (bid)
read (bid sequence)
read (bidder)
read (bidder sequence)
read (item)
read (item sequence)
read (money)
read (time)
resolveAuction
subtract

… and Checking It (at least twice)

… then we can pretty much assign these to our modules just by

Bids
Bid, BidSequence, bids, read bid, read bid seq.
Bidders
Bidder, BidderSequence, bidders, findBidder, read bidder, read bidder seq.
Items
Item, ItemSequence, items, read item, read item seq.
Money
Money, add, equal, lessThan, print, read, subtract
Time
Time, noLaterThan, print, read
??
resolveAuction, main

The “application”

The final two functions, resolveAuction and main, constitute the core algorithm, the “main application” for this particular program, and so can be kept in the main cpp file.

5.1 Dividing Things Up

We can now prepare the modular version of this program.


Headers are for Sharing

We can reduce possible coupling, though, by looking at the actual function bodies and


Not Shared

For example, in the Items module, the function read that reads a single item is only called from inside the function read that reads an entire sequence of items.


Possible Division


Main Auction Program

../cppProgramStructure-auction/auctionMain.cpp
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

#include "items.h"
#include "bidders.h"
#include "bids.h"
#include "times.h"
#include "sequence.h"


/**
 * Determine the winner of the auction for item number i.
 * Announce the winner and remove money from winner's account.
 */
void resolveAuction (const BidSequence& bids, BidderSequence& bidders, const Item& item);



int main (int argc, char** argv)
{
  if (argc != 4)
    {
      cerr << "Usage: " << argv[0] << " itemsFile biddersFile bidsFile" << endl;
      return -1;
    }

  {
	  ifstream itemInput (argv[1]);
	  read (itemInput, items);
  }
  {
	  ifstream bidderInput (argv[2]);
	  read (bidderInput, bidders);
  }
  {
	  ifstream bidInput (argv[3]);
	  read (bidInput, bids);
  }


  for (int i = 0; i < items.size; ++i)
    {
      resolveAuction(bids, bidders, items.data[i]);
    }
  return 0;
}


/**
 * Determine the winner of the auction for item number i.
 * Announce the winner and remove money from winner's account.
 */
void resolveAuction (const BidSequence& bids, BidderSequence& bidders, const Item& item)
{
  Money zero = {0, 0};
  Money highestBidSoFar = zero;
  string winningBidderSoFar;
  for (int bidNum = 0; bidNum < bids.size; ++bidNum)
    {
      Bid bid = bids.data[bidNum];
      if (noLaterThan(bid.bidPlacedAt, item.auctionEndsAt))
        {
          if (bid.itemName == item.name
            && lessThan(highestBidSoFar, bid.amount)
            && !lessThan(bid.amount, item.reservedPrice)
            )
            {
              int bidderNum = findBidder(bid.bidderName, bidders);
              Bidder bidder = bidders.data[bidderNum];
              // Can this bidder afford it?
              if (!lessThan(bidder.balance, bid.amount))
                {
                  highestBidSoFar = bid.amount;
                  winningBidderSoFar = bid.bidderName;
                }
            }
        }
    }

    // If highestBidSoFar is non-zero, we have a winner
    if (lessThan(zero, highestBidSoFar))
      {
        int bidderNum = findBidder(winningBidderSoFar, bidders);
        cout << item.name
             << " won by " << winningBidderSoFar
             << " for $";
        print (cout, highestBidSoFar);
        cout << endl;
        bidders.data[bidderNum].balance =
        		subtract (bidders.data[bidderNum].balance, highestBidSoFar);
      }
    else
      {
        cout << item.name
             << " reserve not met"
             << endl;
      }
}