CSS 343: Notes from Lecture 14 (DRAFT)

Administrivia

Our Story So Far

Static Members

Virtual Methods

Function Pointers

A declaration like this:

Hobbit* frodo;

declares a variable named frodo which holds a pointer to a Hobbit, which, we assume, is some declared type.

Similarly, this

Hobbit* make_hobbit(const char* name);

declares a function named make_hobbit that takes a C-style string and returns a pointer to a Hobbit, which we still assume is some declared type.

Recall that, although current style is to write the frodo declaration as above, it's really

Hobbit *frodo;

That is, *frodo is a Hobbit (i.e. dereferencing frodo gives you a Hobbit. In other words, we could declare frodo like this:

Hobbit (*frodo);

The parentheses are optional, but we introduce optional parentheses into expressions for clarity, all the time.

The declaration for make_hobbit could also be written like this:

Hobbit* (make_hobbit(const char* name));

or like this

Hobbit* (make_hobbit)(const char* name);

or even like this

Hobbit* ((make_hobbit)(const char* name));

Now, this

Hobbit* (*hobbit_factory)(const char* name);

declares hobbit_factory to be a pointer to a function that takes a C-string and returns a Hobbit pointer

A function pointer (or pointer-to-a-function) is a pointer, which means it's essentially a memory address. In fact, it's the memory address of the first instruction of the function, the target of the machine jump instruction that calls the function.

Pointer arithmetic is not meaningful for function pointers because they have no defined sizeof() value, but you can take the address of a function, and you can dereference a function pointer. Dereferencing a function pointer means calling the function

This

Hobbit* frodo;
Hobbit* make_hobbit(const char* name);
frodo = make_hobbit("Frodo Baggins");

is the same as

Hobbit* frodo;
Hobbit* make_hobbit(const char* name);
Hobbit* (*hobbit_factory)(const char* name);
hobbit_factory = &make_hobbit;
frodo = (*hobbit_factory)("Frodo Baggins");

Finally, observe that dereferencing a function pointer means making a call and making a function call means supplying arguments (in parentheses), the & and * are superfluous and optional (because the language designer decided to explicitly make it optional):

Hobbit* frodo;
Hobbit* make_hobbit(const char* name);
Hobbit* (*hobbit_factory)(const char* name);
hobbit_factory = make_hobbit;
frodo = hobbit_factory("Frodo Baggins");

Note: for more on interpreting complex declarations, see http://cdecl.org/

Applications

So, the obvious question is, why do this? There are at least three useful applications:

  1. jump tables
  2. callbacks
  3. dynamic behavior

Jump Table

We can function pointers to dynamically decide which function to call, for example based on a lookup table

Player* make_hobbit(const char* name);
Player* make_elf(const char* name);
Player* make_wizard(const char* name);

map<string, Player* (*)(const char* name)> player_factory;
player_factory["hobbit"] = make_hobbit;
player_factory["elf"] = make_elf;
player_factory["wizard"] = make_wizard;

string player_kind;
cout << "enter character kind: ";
cin >> player_kind;

Player* (*make_player)(const char* name) = player_factory[player_kind.c_str()];
if (make_player == NULL) {
  cout << "invalid player kind";
  exit(1);
}

string player_name;
cout << "enter character name: ";
cin >> player_name;

Player* player = make_player(player_name.c_str());
  

Callbacks

Frameworks, such as graphical user interfaces, provide widgets such as canvases and buttons to which the application wishes to bind behavior.

void do_click() {
  make_alert("do not press me again");
}

Button* button = make_button(canvas, x, y);
button->on_click(do_click);

Dynamic behavior

The cannonical example is shapes:

void draw_circle(Shape* shape);
void draw_triangle(Shape* shape);
void draw_square(Shape* shape);

enum ShapeKind {
//...
};

struct shape {
  ShapeKind tag;
  void (*draw)(Shape*);
  union {
    Circle circle;
    Triangle triangle;
    Square square;
  } data;
};

Shape* shape = make_triangle();
//...
shape->draw(shape);
  

Virtual Methods (cont.)

Function pointers are a powerful construct, but also a bit unwieldy. C++ defines virtual methods to provide similar dynamic behavior.

Before we get into the details, we make an observation. Class Player below has a pointer (8 bytes), int (4 bytes), and bool (1 byte) members. Total size is 13, rounded up with padding to 16 for alignment (size is 12 when compiled for an architecture that has 4-byte pointers).

// middle-earth.cc
//
// Copyright 2013 Systems Deployment, LLC
// Author: Morris Bernstein (morris@systems-deployment.com)

#include <iostream>

using namespace std;


class Player {
public:
  explicit Player(const char* name);
  ~Player();
  void attack(Player* other);
  void hit(int damage);
  void health();

  const char* name_;
  int health_;
  bool alive_;
};


int main() {
  cout << "size of pointer: " << sizeof(char*) << endl;
  cout << "size of int: " << sizeof(int) << endl;
  cout << "size of bool: " << sizeof(bool) << endl;
  cout <<  "size of Player: " << sizeof(Player) << endl;

  return 0;
}
      

size of pointer: 8
size of int: 4
size of bool: 1
size of Player: 16
  

If we make a tiny change, to declare some of Player's methods virtual, the size becomes 24:

// middle-earth.cc
//
// Copyright 2013 Systems Deployment, LLC
// Author: Morris Bernstein (morris@systems-deployment.com)

#include <iostream>

using namespace std;


class Player {
public:
  explicit Player(const char* name);
  ~Player();
  virtual void attack(Player* other);
  virtual void hit(int damage);
  void health();

  const char* name_;
  int health_;
  bool alive_;
};


int main() {
  cout << "size of pointer: " << sizeof(char*) << endl;
  cout << "size of int: " << sizeof(int) << endl;
  cout << "size of bool: " << sizeof(bool) << endl;
  cout <<  "size of Player: " << sizeof(Player) << endl;

  return 0;
}
      

size of pointer: 8
size of int: 4
size of bool: 1
size of Player: 24
  

Why? We'll come back to it shortly. For now, just note that one of the design principles of C++ is that unused features should not impose overhead, and clearly virtual methods impose some sort of overhead.

First, we need to talk about inheritance...

Inheritance

One of the principles of object-oriented programming is that a class can inherit stuff from another class. The class inherited from is called the base class or superclass. The inheriting class called the derived class or subclass.

Without requiring any casting or special syntax, it is possible to assign a pointer to an object of a derived class to a variable of type base-class pointer.

class Derived {
};

class Derived: public Derived {
};

Derived d;
Base* bp = &d;
  

Additionally, note that deleting frodo invokes the Player destructor. More about that shortly.

// middle-earth.cc
//
// Copyright 2013 Systems Deployment, LLC
// Author: Morris Bernstein (morris@systems-deployment.com)

#include <iostream>

using namespace std;


const char* Health[10] = {
  "about to die",
  "feeling woozy",
  "having a really bad day",
  "needs a vacation badly",
  "",
  "",
  "hurting badly",
  "hurting",
  "slightly under the weather",
  "fit as a fiddle"
};


class Weapon {
public:
};


class Dagger : public Weapon {
};


class Player {
public:
  explicit Player(const char* name) : name_(name), health_(99), alive_(true) {}
  ~Player();
  void attack(Player* other);
  void hit(int damage);
  void health();
  const char* name() {return name_;}

  const char* name_;
  int health_;
  bool alive_;
};


class Hobbit: public Player {
public:
  Hobbit(const char* name) : Player(name) {}
  Dagger dagger;
};


class Orc : public Player {
public:
  Orc(const char* name) : Player(name) {}
};


void Player::attack(Player* other) {
  cout << "Player: " << name() << " attacks " << other->name() << endl;
  other->hit(25);
}


void Player::hit(int damage) {
  cout << "Player: " << name()  << " takes " << damage << " hitpoints." << endl;
  health_ -= damage;
  if (health_ <= 0) {
    cout << "Player: " << name() << "dies" << endl;
    alive_ = false;
  }
}


void Player::health() {
  cout << "Player: " << name() << " is " << Health[health_/10] << endl;
}


int main() {
  Player* frodo = new Hobbit("Frodo Baggins");
  Player* random_orc = new Orc("some random orc");

  frodo->attack(random_orc);
  random_orc->health();

  return 0;
}
      

Player: Frodo Baggins attacks some random orc
Player: some random orc takes 25 hitpoints.
Player: some random orc is hurting
  

Now we can start to do some interesting things. frodo->hit() behaves differently than player->hit() even though both variables point to the same object.

// middle-earth.cc
//
// Copyright 2013 Systems Deployment, LLC
// Author: Morris Bernstein (morris@systems-deployment.com)

#include <iostream>

using namespace std;


const char* Health[10] = {
  "about to die",
  "feeling woozy",
  "having a really bad day",
  "needs a vacation badly",
  "",
  "",
  "hurting badly",
  "hurting",
  "slightly under the weather",
  "fit as a fiddle"
};


class Weapon {
public:
};


class Dagger : public Weapon {
public:
  Dagger(bool is_sting=false);

  bool is_sting_;
};


class Player {
public:
  explicit Player(const char* name);
  ~Player();
  void attack(Player* other);
  void hit(int damage);
  void health();
  const char* name() {return name_;}

  const char* name_;
  int health_;
  bool alive_;
};


class Hobbit: public Player {
public:
  Hobbit(const char* name, bool has_sting);
  void attack(Player* other);

  Dagger dagger_;
};


class Orc : public Player {
public:
  Orc(const char* name);
};


Dagger::Dagger(bool is_sting) : is_sting_(is_sting) {
}


Player::Player(const char* name) : name_(name), health_(99), alive_(true) {
}


Player::~Player() {
  cerr << "Player: deleting " << name() << endl;
}


void Player::attack(Player* other) {
  cout << "Player: " << name() << " attacks " << other->name() << endl;
  other->hit(16);
}


void Player::hit(int damage) {
  cout << "Player: " << name()  << " takes " << damage << " hitpoints." << endl;
  health_ -= damage;
  if (health_ <= 0) {
    cout << "Player: " << name() << "dies" << endl;
    alive_ = false;
  }
}


void Player::health() {
  cout << "Player: " << name() << " is " << Health[health_/10] << endl;
}


Hobbit::Hobbit(const char* name, bool has_sting) : Player(name), dagger_(has_sting) {
  cout << "Hobbit: creating " << this << " (" << name << ")" << endl;
}


void Hobbit::attack(Player* other) {
  int damage = 12;
  cout << "Hobbit: " << name()  << " hits " << other->name();
  if (dagger_.is_sting_) {
    cout << " with sting";
    damage = 48;
  }
  cout << endl;
  other->hit(damage);
}


Orc::Orc(const char* name) : Player(name) {
}


int main() {
  Hobbit* frodo = new Hobbit("Frodo Baggins", true);
  Player* random_orc = new Orc("some random orc");

  Player* player = frodo;
  player->attack(random_orc);
  random_orc->health();

  frodo->attack(random_orc);
  random_orc->health();

  delete random_orc;
  delete frodo;

  return 0;
}
      

Hobbit: creating 0xa1f010 (Frodo Baggins)
Player: Frodo Baggins attacks some random orc
Player: some random orc takes 16 hitpoints.
Player: some random orc is slightly under the weather
Hobbit: Frodo Baggins hits some random orc with sting
Player: some random orc takes 48 hitpoints.
Player: some random orc is needs a vacation badly
Player: deleting some random orc
Player: deleting Frodo Baggins
  

How does this work under the hood? The compiler automagically creates an object of the base class as the first field of the derived object. When you assign to a variable of the base time, the compiler is doing an implicit cast. Since the base object and the derived object overlap in memory, everything works out nicely (aren't you glad we spent time at the beginning of the quarter trying to understand casting!). The situation is only slightly more complicated for multiple inheritance.

The extra int field pipes_smoked was added to the Hobbit class because the 1-byte bool value in Dagger field would otherwise get absorbed into the padding of the Player object hidden inside the Hobbit object and your humble instructor did not want to confuse the issue.

// middle-earth.cc
//
// Copyright 2013 Systems Deployment, LLC
// Author: Morris Bernstein (morris@systems-deployment.com)

#include <iostream>

using namespace std;


class Weapon {
public:
};


class Dagger : public Weapon {
public:
  Dagger(bool is_sting=false);

  bool is_sting_;
};


class Player {
public:
  explicit Player(const char* name);
  ~Player();
  void attack(Player* other);
  void hit(int damage);
  void health();
  const char* name() {return name_;}

  const char* name_;
  int health_;
  bool alive_;
};


class Hobbit: public Player {
public:
  Hobbit(const char* name, bool has_sting);
  void attack(Player* other);

  int pipes_smoked;
  Dagger dagger_;
};


class Orc : public Player {
public:
  Orc(const char* name);
};


int main() {
  cout << "size of pointer: " << sizeof(char*) << endl;
  cout << "size of int: " << sizeof(int) << endl;
  cout << "size of bool: " << sizeof(bool) << endl;
  cout << "size of Weapon: " << sizeof(Weapon) << endl;
  cout << "size of Dagger: " << sizeof(Dagger) << endl;
  cout << "size of Player: " << sizeof(Player) << endl;
  cout << "size of Hobbit: " << sizeof(Hobbit) << endl;
  cout << "size of Orc: " << sizeof(Orc) << endl;

  return 0;
}
      

size of pointer: 8
size of int: 4
size of bool: 1
size of Weapon: 1
size of Dagger: 1
size of Player: 16
size of Hobbit: 24
size of Orc: 16
  

Now, consider the question of why the Player destructor is called when destructing a Hobbit object.

Recall that low-level mucking around in memory for assignment 1? This is tedious and error-prone. The C++ language wants object instantiation to result in well-formed object. When an object is created, the constructor is called for each data member, in order before calling the constructor for the object itself. This includes the hidden base object which comes first. If any of those object have fields with constructors, those constructors are called recursively. Think of it as a post-order tree traversal.

When the destructor is called, the destructors of each of the data member is called in the reverse order of the construction.

This behavior is made to happen automagically by the compiler according to the language standard. It's important (and will probably show up on the final exam) to understand the behavior.

// middle-earth.cc
//
// Copyright 2013 Systems Deployment, LLC
// Author: Morris Bernstein (morris@systems-deployment.com)

#include <iostream>

using namespace std;


const char* Health[10] = {
  "about to die",
  "feeling woozy",
  "having a really bad day",
  "needs a vacation badly",
  "",
  "",
  "hurting badly",
  "hurting",
  "slightly under the weather",
  "fit as a fiddle"
};


class Weapon {
public:
  Weapon();
  ~Weapon();
};


class Dagger : public Weapon {
public:
  Dagger(bool is_sting=false);
  ~Dagger();

  bool is_sting_;
};


class Player {
public:
  explicit Player(const char* name);
  ~Player();
  void attack(Player* other);
  void hit(int damage);
  void health();
  const char* name() {return name_;}

  const char* name_;
  int health_;
  bool alive_;
};


class Hobbit: public Player {
public:
  Hobbit(const char* name, bool has_sting);
  ~Hobbit();
  void attack(Player* other);

  Dagger dagger_;
};


Weapon::Weapon() {
  cout << "Weapon: constructor" << endl;
}


Weapon::~Weapon() {
  cout << "Weapon: destructor" << endl;
}


Dagger::Dagger(bool is_sting) : is_sting_(is_sting) {
  cout << "Dagger: constructor (is_sting is " << is_sting_ << ")" << endl;
}


Dagger::~Dagger() {
  cout << "Dagger: destructor" << endl;
}


Player::Player(const char* name) : name_(name), health_(99), alive_(true) {
  cout << "Player: constructing " << name_ << endl;
}


Player::~Player() {
  cerr << "Player: deleting " << name() << endl;
}


Hobbit::Hobbit(const char* name, bool has_sting) : Player(name), dagger_(has_sting) {
  cout << "Hobbit: creating " << this << " (" << name_ << ")" << endl;
}

Hobbit::~Hobbit() {
  cout << "Hobbit: deleting " << this << " (" << name_ << ")" << endl;
}


int main() {
  cout << "main(): creating hobbit" << endl;
  Hobbit* frodo = new Hobbit("Frodo Baggins", true);

  cout << endl;

  cout << "main(): deleting hobbit" << endl;
  delete frodo;

  return 0;
}
      

main(): creating hobbit
Player: constructing Frodo Baggins
Weapon: constructor
Dagger: constructor (is_sting is 1)
Hobbit: creating 0x1fe0010 (Frodo Baggins)

main(): deleting hobbit
Hobbit: deleting 0x1fe0010 (Frodo Baggins)
Dagger: destructor
Weapon: destructor
Player: deleting Frodo Baggins
  

Virtual Methods

Recall the example above, where the behavior depends on the type of the variable. If we make the method virtual, the behavior depends on the type of the data.

// middle-earth.cc
//
// Copyright 2013 Systems Deployment, LLC
// Author: Morris Bernstein (morris@systems-deployment.com)

#include <iostream>

using namespace std;


const char* Health[10] = {
  "about to die",
  "feeling woozy",
  "having a really bad day",
  "needs a vacation badly",
  "groans",
  "says, \"ouch\"",
  "hurting badly",
  "hurting",
  "slightly under the weather",
  "fit as a fiddle"
};


class Weapon {
public:
};


class Dagger : public Weapon {
public:
  Dagger(bool is_sting=false);

  bool is_sting_;
};


class Player {
public:
  explicit Player(const char* name);
  ~Player();
  virtual void attack(Player* other);
  void hit(int damage);
  void health();
  const char* name() {return name_;}

  const char* name_;
  int health_;
  bool alive_;
};


class Hobbit: public Player {
public:
  Hobbit(const char* name, bool has_sting);
  virtual void attack(Player* other);

  Dagger dagger_;
};


class Orc : public Player {
public:
  Orc(const char* name);
};


Dagger::Dagger(bool is_sting) : is_sting_(is_sting) {
}


Player::Player(const char* name) : name_(name), health_(99), alive_(true) {
}


Player::~Player() {
  cerr << "Player: deleting " << name() << endl;
}


void Player::attack(Player* other) {
  cout << "Player: " << name() << " attacks " << other->name() << endl;
  other->hit(16);
}


void Player::hit(int damage) {
  cout << "Player: " << name()  << " takes " << damage << " hitpoints." << endl;
  health_ -= damage;
  if (health_ <= 0) {
    cout << "Player: " << name() << "dies" << endl;
    alive_ = false;
  }
}


void Player::health() {
  cout << "Player: " << name() << " is " << Health[health_/10] << endl;
}


Hobbit::Hobbit(const char* name, bool has_sting) : Player(name), dagger_(has_sting) {
  cout << "Hobbit: creating " << this << " (" << name << ")" << endl;
}


void Hobbit::attack(Player* other) {
  int damage = 12;
  cout << "Hobbit: " << name()  << " hits " << other->name();
  if (dagger_.is_sting_) {
    cout << " with sting";
    damage = 48;
  }
  cout << endl;
  other->hit(damage);
}


Orc::Orc(const char* name) : Player(name) {
}


int main() {
  Hobbit* frodo = new Hobbit("Frodo Baggins", true);
  Player* random_orc = new Orc("some random orc");

  Player* player = frodo;
  player->attack(random_orc);
  random_orc->health();

  frodo->attack(random_orc);
  random_orc->health();

  delete random_orc;
  delete frodo;

  return 0;
}
      

Hobbit: creating 0x227b010 (Frodo Baggins)
Hobbit: Frodo Baggins hits some random orc with sting
Player: some random orc takes 48 hitpoints.
Player: some random orc is says, "ouch"
Hobbit: Frodo Baggins hits some random orc with sting
Player: some random orc takes 48 hitpoints.
Player: some random orc is about to die
Player: deleting some random orc
Player: deleting Frodo Baggins
  

When the behavior depends on the time of the data rather than the type of the variable, it's known as polymorphism. In a statically-typed language like C++ or Java, polymorphism works through inheritance.

The mechanism for making this work is actually rather clever. When the class has any virtual methods, the compiler adds a hidden field at the beginning of the object called the vptr or virtual pointer which points to a table of function pointers, called the vtbl (virtual table). There is a vtbl for each class. The virtual methods are entered into the table in the order of their declaration.

Pure Virtual Methods and Abstract Base Classes

A method declared like this:

Weapon* wield(bool hand) = 0;

is called pure virtual and says that is intended to be overridden by a derived class. Classes with a pure virtual method cannot be instatiated; they are intended only to be used as a base class. Such classes are called pure virtual.