static
this
pointer
obj_ptr->my_static()
MyClass::my_static()
this
there
virtual
keyword
virtual
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/
So, the obvious question is, why do this? There are at least three useful applications:
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());
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);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);
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...
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
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.
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.