Abstract Data Types in C++

As we move onward to the C++ style for ADTs, we'll use class rather than struct and also use built-in features of the language (i.e. things that C++ adds beyond C) to support good practices in a more robust way. In particular, a class in C++ gives us:

  1. Member Functions
    Both data (i.e. member variables) and behaviors (i.e. member functions) for an ADT are encapsulated as members of a class.

  2. Member Access Levels
    Give public access to an ADT's interface, e.g. functions we want other parts of our code to call while at the same time restricting internal details like raw member data or helper functions to private access.

  3. Constructors
    Use constructors to ensure ADTs are always initialized (rather than having to remember to separately call an _Init() function).

Updated Fall 2024

1: Warm Up Exercise
1.1 Not Started

Let's start today with a quick exercise that helps motivate the transition we'll make from C-Style to C++ Style ADTs.

1.1 Exercise: Warm Up

Consider the code here that creates and uses a C-style ADT, specifically the Triangle ADT from last time:

int main() {
  Triangle t; // line 2
  Triangle_init(&t, 3, 4, 5); // line 3
  cout << Triangle_perimeter(&t) << endl;
  Triangle_scale(&t, 2);
  cout << t.a << endl; // line 6
}
What would happen if the programmer forgot to write line 2?
What would happen if the programmer forgot to write line 3?
Which parameter do all of the Triangle_ functions have in common?
What's wrong with line 6? Does the compiler give us an error here?


You're welcome to check your solution with this walkthrough video:




2: Introduction to Classes and Member Functions
2.1 Not Started

The C++ builds on the C language by adding language features to formalize several of the patterns for ADTs that we've used so far. We'll call this the "C++ Style" for ADTs and generally switch to using class rather than struct.

Here's an introduction:


2.1 Exercise: halfPerimeter()

Consider another member function, halfPerimeter(), which is intended to return a value that is half of the triangle's perimeter. The (questionable) algorithm we choose for our implementation is to first shrink the triangle in half and then return its perimeter.

The lines shrink(2); and return perimeter(); call member functions, but what object are they called on?
The compiler says there's some kind of const error with the shrink(2); line. Try adding const to the signature of shrink. Does this fix the problem? (Hint: Nope. But why not?)
Try removing the const on halfPerimeter(). Now the code compiles. Are there any situations in which calling halfPerimeter() from main() wouldn't compile now?
The original call to t1.halfPerimeter() compiles now, but what's wrong with the code? What does this mean about using const and the algorithm we chose for halfPerimeter() (i.e. shrink then return perimeter)?


You're welcome to check your solution with this walkthrough video:




3: Member Access Levels

In C++, you can separate member declarations into different access levels (public vs. private).


(The exercise following the next section on constructors will also incorporate member access levels.)



4: Constructors
4.1 Not Started

The C++ style also uses constructors as a formal mechanism for initialization.


4.1 Exercise: Coffee class

Consider the class below, used as an ADT for a cup of coffee. Note that the implementations for the member functions are omitted for brevity.

class Coffee {
private:
  int creams;
  int sugars;
  bool isDecaf;

public:
  // Construct a coffee with the given details
  Coffee(int creams, int sugars, bool isDecaf);

  // This constructor internally defaults decaf to false
  Coffee(int creams, int sugars);

  // Adds one more cream to the coffee
  void addCream();

  // Adds one more sugar to the coffee
  void addSugar();

  // Prints a description of the coffee
  void print() const;
};

Consider each of the following code snippets that we might write in a main function to use the Coffee class? If the compiler would allow the code, write "ok". Otherwise, write "error" and a very brief explanation of the problem.

Coffee c1;
c1.addCream();
c1.print();
Coffee c2(2, 2);
if (c2.isDecaf) {
  c2.print();
}
Coffee c3(2, 2, false);
c3.addCream();
c3.print();
const Coffee c4(0, 0);
c4.addCream();
c4.print();
Coffee c5(true);
c5.addSugar();
c5.print();


You're welcome to check your solution with this walkthrough video:




5: Implicitly Defined Constructors
5.1 Not Started

There are a few constructors the compiler may automatically provide for your classes.


5.1 Exercise: Default Constructors

Consider each of the following classes. Are they default-constructible (i.e. can you define a default-initialized variable with that class type)? Why or why not?

class Student {
private:
  string name;
  int num_credits;

public:
  Student(const string &name, int num_credits)
    : name(name), num_credits(num_credits) { }
};

int main() {
  // Would this be allowed?
  Student s;
}
Write "ok" if the class is default-constructible. Otherwise, write "error". Justify your answer.
class Pickle {
private:
  bool is_sweet;
  bool is_sour;
  bool is_spicy;

public:
  Pickle()
    : Pickle(false, false, false) { }

  Pickle(bool is_sweet, bool is_sour, bool is_spicy)
    : is_sweet(is_sweet),
      is_sour(is_sour),
      is_spicy(is_spicy) { }
};

int main() {
  // Would this be allowed?
  Pickle p;
}
Write "ok" if the class is default-constructible. Otherwise, write "error". Justify your answer.
class Cow {
private:
  string name;
  int num_spots;
};

int main() {
  // Would this be allowed?
  Cow c;
}
Write "ok" if the class is default-constructible. Otherwise, write "error". Justify your answer.


You're welcome to check your solution with this walkthrough video:




6: Composing C++ ADTs (Classes as Members)
6.1 Not Started

As we did for C-style ADTs, let's take a look at composing more complex ADTs in C++ as classes with other classes for member variables. In this case, we need to ensure that the constructor for the outside class calls each of the constructors for its members (and the compiler double checks this for us).


6.1 Exercise: Professor Constructors

Here again are the classes from the video:

class Coffee {
public: 
  Coffee(int creams, int sugars);
  Coffee(int creams, int sugars,
        bool isDecaf);
};

class Triangle {
public:
  Triangle();
  Triangle(double side);
  Triangle(double a_in, double b_in,
          double c_in);
};

class Professor {
private:
  string name;
  vector<string> students;
  Coffee favCoffee;
  Triangle favTriangle;
  ...
};

Consider several possible constructors for the Professor class. If the constructor definition would compile successfully, write "ok". Otherwise, write "error" and a very brief explanation of the problem (including which member is not initialized correctly).

Professor(const string &name)
 : name(name) { }
Professor(int creams, int sugars)
 : favCoffee(creams, sugars) { }
Professor(const string &name, const string &student)
 : name(name) {
  students.push_back(student);
}
Professor(const Coffee &coffee)
 : name("Laura"), favCoffee(coffee), favTriangle(3, 5) { }


You're welcome to check your solution with this walkthrough video. (Please accept my apologies for the notification sounds in the video… apparently some group chat of mine was going nuts.)




7: Best Practices for C++ ADTs

Finally, let's consider some more miscellaneous topics and best practices for writing well-designed classes, including:

  • The difference between struct and class in C++
  • Why you should always use member initializer lists in constructors
  • Using assertions to double check representation invariants
  • Separating classes into .h and .cpp files
  • Testing C++ style ADTs


You've reached the end of this lecture! Your work on any exercises will be saved if you re-open this page in the same web browser.

Participation Credit
Make sure to sign in to the page, complete each of the exercises, and double check the participation indicator at the top left of this page to ensure you've earned credit.