Encapsulation

Steven Zeil

Last modified: Jul 04, 2014

Contents:
1. Encapsulation
1.1 Encapsulation in C++
2. Classes
3. Hiding Attributes
4. Inline Functions
4.1 How Functions Work
4.2 How Function Calls Work
4.3 Inline Functions
5. Example: the Auction Program as Encapsulated ADTs

1. Encapsulation


The ADT as Contract

When an ADT specification/implementation is provided to users (application programmers):


Enforcing the Contract

1.1 Encapsulation in C++


Encapsulation in C++

We can mark portions of a struct as


Time for Encapsulation

timeStruct.h
struct Time {
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours();
  int getMinutes();
  int getSeconds();

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


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

  /**
   *  Compare two times. Return true iff time1 is earlier than or equal to
   *  time2
   */
  bool noLaterThan(const Time& time2);


  /**
   *  Compare two times. Return true iff time1 is equal to
   *  time2
   *
   */
  bool equalTo(const Time& time2);

private:
  // From here on is hidden
  int secondsSinceMidnight;
};

int Time::getHours()
{
  return secondsSinceMidnight / 3600; // OK
}

int getHours(Time t)
{
  return t.secondsSinceMidnight / 3600; // Error
}

2. Classes


From structs to classes


Showing a Little Class

Our time class:

timeClass.h
class Time{
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours();
  int getMinutes();
  int getSeconds();

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


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

  /**
   *  Compare two times. Return true iff time1 is earlier than or equal to
   *  time2
   */
  bool noLaterThan(const Time& time2);


  /**
   *  Compare two times. Return true iff time1 is equal to
   *  time2
   *
   */
  bool equalTo(const Time& time2);

private:
  // From here on is hidden
  int secondsSinceMidnight;
};

And our alternate version of the same class:

timeClass2.h
class Time {
  // The declaration below is hidden
  int secondsSinceMidnight;
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours();
  int getMinutes();
  int getSeconds();

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


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

  /**
   *  Compare two times. Return true iff time1 is earlier than or equal to
   *  time2
   */
  bool noLaterThan(const Time& time2);


  /**
   *  Compare two times. Return true iff time1 is equal to
   *  time2
   *
   */
  bool equalTo(const Time& time2);
};

3. Hiding Attributes


Hide Your Attributes

An almost universal rule of thumb for ADTs in C++:

All data members should be private.


Example: Time Attributes

timeAttr.h
class Time {
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours();
  void setHours (int h);
  int getMinutes();
  void setMinutes (int m);
  int getSeconds();
  void setSeconds (int s);
  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


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

  /**
   *  Compare two times. Return true iff time1 is earlier than or equal to
   *  time2
   */
  bool noLaterThan(const Time& time2);


  /**
   *  Compare two times. Return true iff time1 is equal to
   *  time2
   *
   */
  bool equalTo(const Time& time2);
private:
  // From here on is hidden
  int hours;
  int minutes;
  int seconds;

};

int Time::getHours()
{
  return hours;
}

void Time::setHours(int h)
{
  hours = h;
}

4. Inline Functions


Are Short Functions Ineffcient?

Are ADT implementations terribly inefficient?

They tend to feature a lot of one-liners and similar short functions.

int Time::getHours()
{
  return hours;
}

4.1 How Functions Work


How Functions Work

int foo(int a, int b)
{
  return a+b-1;
}

would compile into a block of code equivalent to

   stack[1] = stack[3] + stack[2] - 1;
   jump to address in stack[0]


The Runtime Stack

4.2 How Function Calls Work


How Function Calls Work

A function call like

  x = foo(y,z+1);

would be compiled into a code sequence along the lines of

     push y onto the runtime stack;
     evaluate z+1;
     push the result onto the runtime stack
     push (space for the return value) onto the runtime stack
     save all CPU registers
     push address RET onto the runtime stack
     jump to start of foo's body
RET: x = stack[1]
     pop runtime stack 4 times
     restore all CPU registers

As you can see, there’s a fair amount of overhead involved in passing parameters and return address information to a function when making a call.


Overhead

int Time::getHours()
{
  return hours;
}

void Time::setHours(int h)
{
  hours = h;
}

4.3 Inline Functions


Inline Functions

timeInline.h
class Time {
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours() {return hours;}
  void setHours (int h) {hours = h;}
  int getMinutes();
  void setMinutes (int m);
    ⋮
private:
  int hours;
  int minutes;
  int seconds;

};

inline 
int Time::getMinutes()
{
  return minutes;
}

inline
void Time::setMinutes(int m)
{
  minutes = m;
}


How Inline Functions Work

When we make a call to an inline function, the compiler simply replaces the call by a compiled copy of the function body (with some appropriate renaming of variables to avoid conflicts).


Example of an Inline Call

If we have

inline int foo(int a, int b)
{
  return a+b-1;
}

and we later make a call

  x = foo(y,z+1);

This would be compiled into a code sequence along the lines of

   evaluate z+1, storing result in tempB 
   evaluate y + tempB - 1, storing result in x

Most of the overhead of making a function call has been eliminated.


Use Inline For Functions That Are …

If abused, inline calls tend to make the size of the executable program grow.


Inlining is Optional

Inlining is only a recommendation from the programmer to the compiler.

5. Example: the Auction Program as Encapsulated ADTs


The Auction Program

auction/auctionMain.cpp
#include <iostream>
#include <string>

using namespace std;

#include "items.h"
#include "itemcollection.h"
#include "bidders.h"
#include "biddercollection.h"
#include "bids.h"
#include "bidcollection.h"
#include "time.h"

const int MaxBidders = 100;
const int MaxBids = 5000;
const int MaxItems = 100;


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


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

  ItemCollection items (MaxItems);
  items.readItems (argv[1]);

  BidderCollection bidders (MaxBidders);
  bidders.readBidders (argv[2]);

  BidCollection bids (MaxBids);
  bids.readBids (argv[3]);

  for (int i = 0; i < items.getSize(); ++i)
    {
      resolveAuction(items.get(i), bidders, bids);
    }
  return 0;
}


/**
 * Determine the winner of the auction for an item.
 * Announce the winner and remove money from winner's account.
 */
void resolveAuction (const Item& item, 
                     BidderCollection& bidders, 
                     BidCollection& bids)
{
  double highestBidSoFar = 0.0;
  string winningBidderSoFar;
  bool reservePriceMet = false;
  for (int bidNum = 0; bidNum < bids.getSize(); ++bidNum)
    {
      Bid bid = bids.get(bidNum);
      if (bid.getTimePlacedAt().noLaterThan(item.getAuctionEndTime()))
        {
          if (bid.getItem() == item.getName()
              && bid.getAmount() > highestBidSoFar
              )
            {
              int bidderNum = bidders.findBidder(bid.getBidder());
              Bidder bidder = bidders.get(bidderNum);
              // Can this bidder afford it?
              if (bid.getAmount() <= bidder.getBalance())
                {
                  highestBidSoFar = bid.getAmount();
                  winningBidderSoFar = bid.getBidder();
                }
            }
            if (bid.getAmount() > item.getReservedPrice())
                reservePriceMet = true;
        }
    }

    // If highestBidSoFar is non-zero, we have a winner
    if (reservePriceMet && highestBidSoFar > 0.0)
      {
        int bidderNum = bidders.findBidder(winningBidderSoFar);
        cout << item.getName()
             << " won by " << winningBidderSoFar
             << " for " << highestBidSoFar << endl;
        Bidder& bidder = bidders.get(bidderNum);
        bidder.setBalance (bidder.getBalance() - highestBidSoFar);
      }
    else
      {
        cout << item.getName()
             << " reserve not met"
             << endl;
      }
}




auction/bids.h
#ifndef BIDS_H
#define BIDS_H

#include <string>
#include "time.h"


//
//  Bids Received During Auction
//

class Bid {

  std::string bidderName;
  double amount;
  std::string itemName;
  Time bidPlacedAt;

public:

  Bid (std::string bidder, double amt, 
       std::string item, Time placedAt);

  Bid();

  // Access to attribute
  std::string getBidder() const {return bidderName;}
  double getAmount() const {return amount;}
  std::string getItem() const {return itemName;}
  Time  getTimePlacedAt() const {return bidPlacedAt;}

};




#endif

and

auction/bids.cpp
#include <string>
#include <fstream>

using namespace std;

//
//  Bids Received During Auction
//

#include "bids.h"


Bid::Bid (std::string bidder, double amt, 
     std::string item, Time placedAt)
  : bidderName(bidder), amount(amt),
    itemName(item), bidPlacedAt(placedAt)
{
}

Bid::Bid ()
  : amount(0.0)
{
}

auction/bidders.h
#ifndef BIDDERS_H
#define BIDDERS_H

#include <string>

//
//  Bidders Registered for auction
//


class Bidder {
  // describes someone registered to participate in an auction
  std::string name;
  double balance;

public:
  
  Bidder();
  Bidder (std::string theName, double theBalance);

  // Access to attributes
  std::string getName() const {return name;}

  double getBalance() const {return balance;}
  void setBalance (double newBal) {balance = newBal;}
};


#endif

and

auction/bidders.cpp
#include <string>
#include <fstream>
#include <iostream>
//
//  Bidders Registered for auction
//

#include "bidders.h"

using namespace std;



Bidder::Bidder()
{
  name = "";
  balance = 0.0;
}

Bidder::Bidder (std::string theName, double theBalance)
{
  name = theName;
  balance = theBalance;
}

auction/items.h
#ifndef ITEMS_H
#define ITEMS_H

#include <iostream>
#include <string>

#include "time.h"

//
//  Items up for auction
//


class Item {
  std::string name;
  double reservedPrice;
  Time auctionEndsAt;

public:
  Item();
  Item (std::string itemName, double reserve, Time auctionEnd);

  // Access to attribute

  std::string getName() const {return name;}
  
  double getReservedPrice() const {return reservedPrice;}

  Time getAuctionEndTime() const {return auctionEndsAt;}


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

};


#endif

and

auction/items.cpp
#include <iostream>
#include <fstream>

#include "items.h"

//
//  Items up for auction
//

using namespace std;




Item::Item()
  : reservedPrice(0.0)
{
}

Item::Item (std::string itemName, double reserve, Time auctionEnd)
  : name(itemName), reservedPrice(reserve), auctionEndsAt(auctionEnd)
{
}


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

  // 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
  name = string(1,c) + line;
}



auction/time.h
#ifndef TIMES_H
#define TIMES_H

#include <iostream>

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */


struct Time {
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours() const;
  int getMinutes() const;
  int getSeconds() const;

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


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

  /**
   *  Compare two times. Return true iff time1 is earlier than or equal to
   *  time2
   */
  bool noLaterThan(const Time& time2);


  /**
   *  Compare two times. Return true iff time1 is equal to
   *  time2
   *
   */
  bool equalTo(const Time& time2);

  // From here on is hidden
  int secondsSinceMidnight;
};

#endif // TIMES_H

and

auction/time.cpp
#include "time.h"
#include <iomanip>

using namespace std;

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */



  // Create time objects
Time::Time() // midnight
{
  secondsSinceMidnight = 0;
}


Time::Time (int h, int m, int s)
{
  secondsSinceMidnight = s + 60 * m + 3600*h;
}

  // Access to attributes
int Time::getHours() const
{
  return secondsSinceMidnight / 3600;
}

int Time::getMinutes() const
{
  return (secondsSinceMidnight % 3600) / 60;
}

int Time::getSeconds() const
{
  return secondsSinceMidnight % 60;
}

// Calculations with time
void Time::add (Time delta)
{
  secondsSinceMidnight += delta.secondsSinceMidnight;
}
      

Time Time::difference (Time fromTime)
{
  Time diff;
  diff.secondsSinceMidnight = 
    secondsSinceMidnight - fromTime.secondsSinceMidnight;
}
  
  
/**
 * Read a time (in the format HH:MM:SS) after skipping any
 * prior whitepace.
 */
void Time::read (std::istream& in)
{
  char c;
  int hours, minutes, seconds;
  in >> hours >> c >> minutes >> c >> seconds;
  Time t (hours, minutes, seconds);
  secondsSinceMidnight = t.secondsSinceMidnight;
}


/**
 * Print a time in the format HH:MM:SS (two digits each)
 */
void Time::print (std::ostream& out) const
{
  out << setfill('0') << setw(2) << getHours() << ':' 
      << setfill('0') << setw(2) << getMinutes() << ':' 
      << setfill('0') << setw(2) << getSeconds();
}



/**
 *  Compare two times. Return true iff time1 is earlier than or equal to
 *  time2
 */
bool Time::noLaterThan(const Time& time2)
{
  return secondsSinceMidnight <= time2.secondsSinceMidnight;
}



/**
 *  Compare two times. Return true iff time1 is equal to
 *  time2
 *
 */
bool Time::equalTo(const Time& time2)
{
  return secondsSinceMidnight == time2.secondsSinceMidnight;
}

Note how the vast majority of the code is now a collection of encapsulated ADTs, with only a limited amount of very application-specific code left outside.

This is very typical of well-written C++ applications.