Polymorphic Big Five
You need to be very careful with Polymorphic Big Five, because there are a few problems that can occur. Learned these in CS247.
Consider the following example:
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.
Problem 1: Partial Assignment (because of non-virtual
)
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.
How to fix this? Declare Book::operator=
as virtual!
class Book {
...
public:
...
virtual Book& operator=(const Book& other) {
...
}
};
In Text
, we can’t just have the following:
Text& operator=(const Text& other);
- Not a valid Function Override (arguments are different)
Signature must be the following
Text& Text::operator=(const Book& other) {}
However, trying to fix this partial assignment this way introduces 2 new problems:
- We can’t access
other
’s topic, because it’s aBook
, andBook
don’t have topics, onlyTexts
do. - other is a
Book&
, so now, we get the mixed assignment problem…
Problem 2: Mixed Assignment Problem (because of virtual
)
Now, the following is legal:
Comic c{...};
Text t{...};
t = c;
We can see that we can set a Text
to a Comic
, as the Comic
on right-hand side can be implicitly converted to a const Book&
.
Setting operator=
to virtual creates the is the mixed assignment problem, as we can now can set subclass siblings to each other.
In summary
- Non-virtual operator = leads to partial assignment
- virtual operator = mixed assignment
To fix this, we need to restructure the Book
hierarchy:
class AbstractBook {
string title, author;
int length;
protected:
AbstractBook& operator=(const AbstractBook& other) = default;
public:
AbstractBook(...) {}
virtual ~AbstractBook() = 0;
};
- notice that
operator=
is under protected now
To make this abstract, we need a Pure Virtual Method. If no other methods make sense to be pure virtual, can always use destructor.
class Text: public AbstractBook {
string topic;
public:
Text(...) {...}
// implicitly: this is implemented: Text& operator=(const Text& t);
};
How does this fix the two problems?
- Mixed assignment:
operator=
is non-virtual and the implicitly provided copy assignment operator only acceptsText
. - Partial assignment problem:
Text t1{...};
Text t2{...};
AbstractBook& br1 = t1;
AbstractBook& br2 = t2;
br2 = br1; //doesn't compile, AbstractBook:operator= is protected
Only works since
AbstractBook
is an abstract classI am still confused on why the teacher says “only works because it’s abstract class”.
I guess only works is maybe the wrong word. Because we are setting
AbstractBook::operator=
to protected. we are ensuring that we can’t assign books to one another. IfBook
was a concrete class, this would be kind of bad, because we DO want to be able to assign books to one another.
TODO BRAINF-CK IDK WHAT’S GOING ON?? Okay, I am getting brainf-cked, wha
- Is it because of how I saw assignment operator doesn’t get inherited?
class AbstractBook {
string title, author;
int length;
protected:
AbstractBook& operator=(const AbstractBook& other) = default;
public:
AbstractBook(...) {}
virtual ~AbstractBook() {};
};
class Text: public AbstractBook {
string topic;
public:
Text() {}
void test() {
Text t1{};
Text t2{};
AbstractBook& br2 = t2;
t1 = br2; // does NOT compile here,why?
}
};
- From my understanding,
Text
is callingoperator=(const Abstract& other)
, which it has access to because of inheritance. But even making the function public does not fix the problem. “no operator ”=” matches these operands”
Also see Method Safety Levels, weird stuff going on for what it means to be able to “access” something.
Other small problem: Destructor
Consider Text
’s destructor. Implicitly, following happens:
- Destructor body runs (empty)
- Object fields we destructed in reverse decl. order
- Superclass destructor runs
- Space is reclaimed
Because in step 3, Text
’s destructor calls AbstractBook’s destructor, we have a problem: we’ve called a method with no implementation.
Solution: give it an implementation:
AbstractBook::~AbstractBook(){}