This is very important, as it guides the design of everything we discuss this week
class BaseClass {
public:
int get_int_member() { return int_member_; }
std::string get_class_name() {
return "BaseClass"
};
private:
int int_member_;
std::string string_member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
std::vector<int> vector_member_;
std::unique_ptr<int> ptr_member_;
}
BaseClass object
int_member_ string_member_ |
SubClass object
int_member_ string_member_ |
vector_member_ ptr_member_ |
BaseClass subobject
SubClass subobject
class BaseClass {
public:
BaseClass(int member): int_member_{member} {}
private:
int int_member_;
std::string string_member_;
}
class SubClass: public BaseClass {
public:
SubClass(int member, std::unique_ptr<int>&& ptr): BaseClass{member}, ptr_member_{std::move(ptr)} {}
// Won't compile.
SubClass(int member, std::unique_ptr<int>&& ptr): int_member_{member}, ptr_member_{std::move(ptr)} {}
private:
std::vector<int> vector_member_;
std::unique_ptr<int> ptr_member_;
}
class BaseClass {
public:
int get_member() { return member_; }
std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
int subclass_data_;
}
void print_stuff(BaseClass base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
class BaseClass {
public:
int get_member() { return member_; }
std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
int subclass_data_;
}
void print_stuff(BaseClass base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
class BaseClass {
public:
int get_member() { return member_; }
std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() {
return "SubClass";
}
private:
int subclass_data_;
}
void print_stuff(const BaseClass& base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
class BaseClass {
public:
int get_member() { return member_; }
virtual std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string GetClassName() override {
return "SubClass";
}
private:
int subclass_data_;
}
void print_stuff(const BaseClass& base) {
std::cout << base.get_class_name()
<< ' ' << base.get_member()
<< '\n';
}
int main() {
BaseClass base_class;
SubClass subclass;
print_class_name(base_class);
print_class_name(subclass);
}
class BaseClass {
public:
int get_member() { return member_; }
virtual std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
// This compiles. But this is a
// different function to the
// BaseClass get_class_name.
std::string get_class_name() const {
return "SubClass";
}
private:
int subclass_data_;
}
class BaseClass {
public:
virtual std::string get_class_name() {
return "BaseClass"
};
~BaseClass() {
std::cout << "Destructing base class\n";
}
}
class SubClass: public BaseClass {
public:
std::string get_class_name() override {
return "SubClass";
}
~SubClass() {
std::cout << "Destructing subclass\n";
}
}
void print_stuff(const BaseClass& base_class) {
std::cout << base_class.GetClassName()
<< ' ' << base_class.GetMember()
<< '\n';
}
int main() {
auto subclass = static_cast<std::unique_ptr<BaseClass>>(
std::make_unique<SubClass>());
std::cout << subclass->get_class_name();
}
class BaseClass {
public:
int get_member() { return member_; }
virtual std::string get_class_name() {
return "BaseClass"
};
private:
int member_;
}
class SubClass: public BaseClass {
public:
std::string get_class_name() override final {
return "SubClass";
}
private:
int subclass_data_;
}
class Shape {
// Your derived class "Circle" may forget to write this.
virtual void draw(Canvas&) {}
// Fails at link time because there's no definition.
virtual void draw(Canvas&);
// Pure virtual function.
virtual void draw(Canvas&) = 0;
};
// Java-style C++ here
// Don't do this.
auto base = std::vector<BaseClass>();
base.push_back(BaseClass{});
base.push_back(SubClass1{});
base.push_back(SubClass2{});
// Good C++ code
// But there's a potential problem here.
// (*very* hard to spot)
auto base = std::vector<std::unique_ptr<BaseClass>>();
base.push_back(std::make_unique<BaseClass>());
base.push_back(std::make_unique<Subclass1>());
base.push_back(std::make_unique<Subclass2>());
// Simplification of previous slides code.
auto base = std::make_unique<BaseClass>();
auto subclass = std::make_unique<Subclass>();
class BaseClass {
BaseClass(BaseClass&&) = default;
BaseClass& operator=(BaseClass&&) = default;
virtual ~BaseClass() = default;
}
Forgetting this can be a hard bug to spot
int main() {
auto base_class = BaseClass();
auto subclass = SubClass();
auto sub_copy = subclass;
// The following could all be replaced with pointers
// and have the same effect.
const BaseClass& base_to_base{base_class};
// Another reason to use auto - you can't accidentally do this.
const BaseClass& base_to_sub{subclass};
// Fails to compile
const SubClass& sub_to_base{base_class};
const SubClass& sub_to_sub{subclass};
// Fails to compile (even though it refers to at a sub);
const SubClass& sub_to_base_to_sub{base_to_sub};
}
Quiz - What's the static and dynamic types of each of these?
auto dog = Dog();
// Up-cast with references.
Animal& animal = dog;
// Up-cast with pointers.
Animal* animal = &dog;
// What's this (hint: not an up-cast)?
Animal animal{dog};
auto dog = Dog();
auto cat = Cat();
Animal& animal_dog{dog};
Animal& animal_cat{cat};
// Attempt to down-cast with references.
// Neither of these compile.
// Why not?
Dog& dog_ref{animal_dog};
Dog& dog_ref{animal_cat};
auto dog = Dog();
auto cat = Cat();
Animal& animal_dog{dog};
Animal& animal_cat{cat};
// Attempt to down-cast with pointers.
Dog* dog_ref{static_cast<Dog*>(&animal_dog)};
Dog* dog_ref{dynamic_cast<Dog*>(&animal_dog)};
// Undefined behaviour (incorrect static cast).
Dog* dog_ref{static_cast<Dog*>(&animal_cat)};
// returns null pointer
Dog* dog_ref{dynamic_cast<Dog*>(&animal_cat)};
auto dog = Dog();
auto cat = Cat();
Animal& animal_dog{dog};
Animal& animal_cat{cat};
// Attempt to down-cast with references.
Dog& dog_ref{static_cast<Dog&>(animal_dog)};
Dog& dog_ref{dynamic_cast<Dog&>(animal_dog)};
// Undefined behaviour (incorrect static cast).
Dog& dog_ref{static_cast<Dog&>(animal_cat)};
// Throws exception
Dog& dog_ref{dynamic_cast<Dog&>(animal_cat)};
Syntax | Name | Meaning |
---|---|---|
virtual void fn() = 0; | pure virtual | Inherit interface only |
virtual void fn() {} | virtual | Inherit interface with optional implementation |
void fn() {} | nonvirtual | Inherit interface and mandatory implementation |
Note: nonvirtuals can be hidden by writing a function with the same name in a subclass
DO NOT DO THIS
class Base {
virtual LandAnimal& get_favorite_animal();
};
class Derived: public Base {
// Fails to compile: Not all animals are land animals.
Animal& get_favorite_animal() override;
// Compiles: All land animals are land animals.
LandAnimal& get_favorite_animal() override;
// Compiles: All dogs are land animals.
Dog& get_favorite_animal() override;
};
class Base {
virtual void use_animal(LandAnimal&);
};
class Derived: public Base {
// Compiles: All land animals are valid input (animals).
void use_animal(Animal&) override;
// Compiles: All land animals are valid input (land animals).
void use_animal(LandAnimal&) override;
// Fails to compile: Not All land animals are valid input (dogs).
void use_animal(Dog&) override;
};
class Base {
virtual void PrintNum(int i = 1) {
std::cout << "Base " << i << '\n';
}
};
class Derived: public Base {
void PrintNum(int i = 2) override {
std::cout << "Derived " << i << '\n';
}
};
int main() {
Derived derived;
Base& base = derived;
derived.PrintNum(); // Prints "Derived 2"
base->PrintNum(); // Prints "Derived 1"
}
class Animal {...}
class LandAnimal: public Animal {...}
class Dog: public LandAnimals {...}
Dog d;
// Dog() calls LandAnimal()
// LandAnimal() calls Animal()
// Animal members constructed using initialiser list
// Animal constructor body runs
// LandAnimal members constructed using initialiser list
// LandAnimal constructor body runs
// Dog members constructed using initialiser list
// Dog constructor body runs
If a class is not fully constructed, cannot perform dynamic binding
class Animal {...};
class LandAnimal: public Animal {
LandAnimal() {
Run();
}
virtual void Run() {
std::cout << "Land animal running\n";
}
};
class Dog: public LandAnimals {
void Run() override {
std::cout << "Dog running\n";
}
};
// When the LandAnimal constructor is being called,
// the Dog part of the object has not been constructed yet.
// C++ chooses to not allow dynamic binding in constructors
// because Dog::Run() might depend upon Dog's members.
Dog d;
class Animal {...}
class LandAnimal: public Animal {...}
class Dog: public LandAnimals {...}
auto d = Dog();
// ~Dog() destructor body runs
// Dog members destructed in reverse order of declaration
// ~LandAnimal() destructor body runs
// LandAnimal members destructed in reverse order of declaration
// ~Animal() destructor body runs
// Animal members destructed in reverse order of declaration.
class Animal {...};
class LandAnimal: public Animal {
virtual ~LandAnimal() {
Run();
}
virtual void Run() {
std::cout << "Land animal running\n";
}
};
class Dog: public LandAnimals {
void Run() override {
std::cout << "Dog running\n";
}
};
// When the LandAnimal constructor is being called,
// the Dog part of the object has already been destroyed.
// C++ chooses to not allow dynamic binding in destructors
// because Dog::Run() might depend upon Dog's members.
auto d = Dog();