CS247 Lecture 11
Last Time: Virtual, Override, destructors, vptr, vtables This Time: Pure virtual, polymorphic arrays, Polymorphic Big 5
Very Interesting!!:
class Shape {
public:
virtual float getArea() const;
}
class Square: public Shape {
float length;
public:
Square(float length): length{length} {}
float getArea() const override {
return length * length;
}
}
class Circle: public Shape {
float radius;
public:
Circle(float radius): radius{radius} {}
float getArea() const override {
return pi * radius * radius;
}
}If we do not provide an implementation for Shape::getArea(), code won’t link “undefined reference to vtable error”.
We could make Shape::getArea() return 0, or -1 to indicate “no area”, but it’s not natural. Really we want to avoid a definition for Shape::getArea() entirely.
Solution: Declare Shape::getArea() as a pure virtual function. A pure virtual function is allowed to not have an implementation. See pure virtual method.
class Shape{
public:
virtual float getArea() const = 0;
};Declares getArea() as pure virtual by adding = 0. Classes that declare a pure virtual function are called Abstract Class. Abstract classes cannot be instantiated as objects.
A class that overrides all of its parents pure virtual functions is called a concrete class. Concrete classes can be instantiated.
The purpose of abstract classes is to provide a framework to organize and define subclasses.
Polymorphic Arrays
class Vec2 {
int x, y;
public:
Vec2(int x, int y): x{x}, y{y}{}
};
class Vec3: public Vec2{
int z;
public:
Vec3(int x, int y, int z): Vec2{x, y}, z{z}{}
};
void f(Vec2* a){
a[0] = Vec2{7,8};
a[1] = Vec2{9,10};
}
Vec3 myArray[2] = {Vec3{1,2,3}, Vec3{4,5,6}};
f(myArray);What does f expect:

What it actually looks like:
All of Vec2{7,8} is written into myArray[0] . Half of Vec2{9,10} is written into myArray[0]. the other half is myArray[1].
Note
Lesson: Be very careful when using arrays of objects polymorphically. Use an array of pointers:
Vec3* myArray[2];Other solution: vector ofVec3* s
Polymorphic Big 5
Let’s consider Book hierarchy again.
Text t{"polymorphism", "Ross", 500, "C++"};
Text t2{t};Compiler still provides us a copy constructor, that works as expected.
Let’s look at copy/move constructor and assignment operator to see their definition.
Copy Constructor:
Text:: Text(const Text& t): Book{t}, topic{t.topic}{}Calls the copy constructor for the Book portion of Text. t is a const Text&, this is implicitly converted to a const Book&.
Book does not have a default copy constructor. So we need it in the MIL.
Move Constructor:
Text::Text(Text&& t): Book{std::move(t)}, topic{std::move(t.topic)}t and t.topic are lvalues, so we’d invoke the copy constructor if we didn’t use std::move. So we use move!
t is an rvalue reference so we know it is safe to steal title, author, length and topic via using std::move to invoke move constructors. (Since rvalue will be gone, ok to modify them and return a random thing.)
Copy Assignment:
Text& Text::operator=(const Text& t){
Book::operator=(t); // calls the Book assignment operator for book portion
topic = t.topic;
return *this;
}Book::operator=(t);calls the Book assignment operator for the Book portion of this
Move Assignment:
Text& Text::operator=(Text&& t){
Book::operator=(std::move(t));
topic = std::move(topic);
return *this;
}All of these implementations are what the compiler gives by default.
Customize as necessary - for example, if doing manual memory management, you will need to write your own versions of these.
BUT: Are the compiler provided definitions actually that good?
Text t1{"polymorphism", "Ross", 500, "C++"};
Text t2{"programming for babies", "LaurierProf", 100, "Python"};
Book& br1 = t1;
Book& br2 = t2;
br2 = br1;
cout << t2;Title, author and length are set, but topic remains unchanged
Book::operator= is defined as non-virtual. We’re calling operator= method on a reference. We use the static type, call Book::operator=, even though br1 and br2 are referencing texts. ????????????????
Some fields are copied, but not all. This is the partial assignment problem.
class Book{
...
public:
...
virtual Book& operator=(const Book& other){
...
}
}Our usual signature for Text is the following:
virtual Text& operator=(const Text& other);Can we just slap an override on the end of this? NO: signature don’t match in 2 places: return type, parameter type.
- Return type: this is actually okay: A subclass’s override method can return a subclass of the virtual function’s return type (if it’s a pointer or reference)
- Parameter type for overridden functions must match exactly
Signature must be the following:
Text& Text::operator=(const Book& other) {
...
}Problem 1) can’t access other’s topic, because it’s a Book, and Book don’t have topics, only Texts do.
Problem 2) other is a Book&, so now this is legal:
Comic c{...};
Text t{...};
t = c;We can set a Text to a Comic on RHS can be implicitly converted to a const Book&.
This is the mixed assignment problem, which is where you can set subclass siblings to each other.
Non-virtual operator= leads to partial assignment
virtual operator= mixed assignment
To fix this, restructure the book hierarchy.
Arrow to AbstractBook UML: Abstract classes and virtual methods are italicized (with stars).
Post Midterm!!
Next: CS247 Lecture 12