Encapsulation

Steven Zeil

Last modified: Dec 26, 2016
Contents:

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++

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

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

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

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

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

../encapsulation-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;
      }
}



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.