Operator Overloading and Inheritance

We'll start by covering function overloading and operator overloading, two mechanisms in C++ (and many other langugages) that allow the use of single names (or operators) to correspond to potentially many different function implementations depending on the types they are used with.

Next, we'll introduce inheritance, which allows us to derive one class from another, such that the derived class automatically "inherits" member variables and functions from its base class. This is helpful both to save us work (i.e. we avoid duplicating common implementation details by putting them in a base class) and to set up the foundation for meaningful hierarchies of ADTs (more on this in the next lecture).

Updated Fall 2024

1: Function Overloading

In C++, multiple functions are allowed to have the same name, even within the same scope, as long as they have different parameter types and can be distinguished by the compiler (and presumably by human programmers, too!). This is called function overloading.




2: Operator Overloading
2.1 Not Started

We can also apply the concept of overloading to operators as well. For example, the + operator means one thing when it's used on int, something else when it's used on string, and yet another thing when it's used for pointer arithmetic!

In C++, we can also define what an operator should do if used on our own custom classes. Take a look:


2.1 Exercise: Pixel Operator Overloads

Let's upgrade Pixel from project 2 to a class and add some overloaded operators:

  • An overloaded << operator that prints a pixel in the format rgb(R,G,B)
  • An overloaded - operator that computes the squared difference of two pixels

Implement each operator (as a non-member function) so that the code in main works correctly.


Sample solution…

  #include <iostream>
  using namespace std;

  class Pixel {
  public:
    const int r;
    const int g;
    const int b;

    Pixel(int r, int g, int b)
      : r(r), g(g), b(b) { }

  };

  int squared_difference(const Pixel &p1, const Pixel &p2);

  // TASK 1: Add an overloaded operator- that
  // returns the squared difference between two
  // pixels (you can just call squared_difference
  // in your implementation)

  int operator-(const Pixel &p1, const Pixel &p2) {
    return squared_difference(p1, p2);
  }

  // TASK 2: Add an overloaded operator<< that
  // prints out the pixel in this format:
  //   rgb({R},{G},{B})
  ostream &operator<<(ostream &os, const Pixel &p) {
    cout << "rgb(" << p.r << ", " << p.g
          << ", " << p.b << ")";
    return os;
  }

  int main() {
    Pixel p1(174, 129, 255);
    Pixel p2(166, 226, 46);

    cout << "p1: " << p1 << endl; // p1: rgb(174,129,255)
    cout << "p2: " << p2 << endl; // p2: rgb(166,226,46)

    cout << "sq diff: " << p2 - p1 << endl; // sq diff: 531
  }

  // From processing.cpp in P2 starter code
  int squared_difference(const Pixel &p1, const Pixel &p2) {
    int dr = p2.r - p1.r;
    int dg = p2.g - p1.g;
    int db = p2.b - p1.b;
    // Divide by 100 is to avoid possible overflows
    // later on in the algorithm.
    return (dr*dr + dg*dg + db*db) / 100;
  }



3: Delegating Constructors
3.1 Not Started

Before moving on to inheritance, here's a quick miscellaneous topic that we didn't get to last time. Delegating constructors allow one constructor to call another to promote code reuse and overall elegant design.


3.1 Exercise: Rectangle Constructors

Add two additional constructors to the Rectangle class. They should use delegation to call the constructor already provided in the starter code.

  • A default constructor that takes no arguments and initializes a 1x1 rectangle.
  • A constructor that takes in one side length s and then creates an sxs rectangle.


Sample solution…

  #include <iostream>
  using namespace std;

  class Rectangle {
  private:
    double width;
    double height;

  public:
    Rectangle(double w, double h)
    : width(w), height(h) { }

    Rectangle(double s) : Rectangle(s,s) { }

    Rectangle() : Rectangle(1,1) { }

    double area() const { return width * height; }
    double perimeter() const { return 2 * (width + height); }
    void scale(double s) { width *= s; height *= s; }
  };



4: Introduction to Inheritance

Let's start with a bit of motivation for inheritance and a brief introduction to the way it's available as a fundamental language feature in C++.


To recap:

  • A derived class contains the member variables from the base class and may define additional ones.
  • You can call member functions of the base class on any derived class instances.
  • A derived class may "hide" a member function from the base class by defining its own version with the same signature.
  • A derived class constructor must call some version of the base class constructor in its constructor-initializer-list. (Unless there's a default constructor for the base class.)

Let's return to apply inheritance to our Bird, Chicken, and Duck classes as well…




5: Inheritance Details
5.1 Not Started

Finally, let's investigate some "behind-the-scenes" details on how inheritance works.


5.1

Which of the following are true?

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.