Lecture 16

Pure Virtual Method

  • One where the virtual method of the base class is not implemented (only declared)

  • Perhaps because the class is so abstract that it is only meant to be subclassed, never itself instantiated

    • i.e. purpose is to organize subclasses, not create objects

  • Cannot declare a variable of this type or call its constructor.

class Student {
  ...
  public:
    // Method has no (*) impli
    // Called a pure virtual method
    virtual int fees() const = 0;
};

Student s; // would not work (cannot make student objects anymore)

Virtual Method UML Notation:

UML: Virtual and pure virtual methods -> indicated by italics Abstract classes: class name in italics Protected: # Static: Underline

Inheritance and Copy/Move

class Book {
  public:
  // Defines copy/move ctor
};

class Text: public Book {
    string topic;
    public:
    // Does not define copy/move operations
  };

Text t {"Algorithms", "LLRS", 500, "CS"};
Text t2=t; // No copy ctor in Text. What happens?

What happens?

  • Calls Book's copy constructor

  • and then goes field-by-field (i.e. default behaviour for the 'Text' part)

  • Same thing happens for other operations

To write your own operations for subclasses:

Text::Text (const Text &other):
  Book {other}, topic {other.topic} {}
  
Text &Text::operator=(const Text &other) {
  Book::operator=(other);
  topic = other.topic;
  return *this;
}

// This will compile but it is wrong (look at note)
Text::Text(Text &&other):
  Book {other}, topic {other.topic} {}

// Look at this one instead
Text::Text(Text &&other): // **
  Book {std::move(other)}, topic {std::move(other.topic)} {} 

Text &Text::operator=(Text &&other) {
  Book::operator=(std::move(other));
  topic = std::move(other.topic);
  return *this;
}

**Note:

  • other (so is other.topic) may be pointing at an rvalue, but it is a lvalue so long as the function is not returned yet - Thus, this is actually a COPY CONSTRUCTOR not MOVE CONSTRUCTOR

  • std::move(x) forces an lvalue x to be treated as an rvalue so that the "move" versions of the operations run

Operations given above are equivalent to the built-in - specialize as needed

Now consider:

Text t1{...}, t2{...};
Book *pb1 = &t1, *pb2 = &t2;

What if we do *pb1 = *pb2?

Partial assignment occurs: Copies only the book part How can we fix this? Try making operator= virtual

class Book {
  ...
  public:
    virtual Book &operator=(const Book &other) {...}
};

class Text: public Book {
  ...
  public:
    virtual Text &operator=(const Book &other) override {...}
};

Note: For virtual methods, different return types from the parent class virtual methods are OK, but param types must be the same, or it's not an override (and won't compile); violates "is-a" (inheritance means is-a)

Now, assignment of a Book object to a Text object would be allowed

Text t{...};
Book b{...};
t=b; // uses a Book to assign a Text, this is bad (but it compiles)

Comic c{...};
t=c; // really bad (mixed assignment)

If operator= is non-virtual - partial assignment through class pointers occurs, which is BAD If virtual - there is mixed assignment which is BAD

Recommendation: All superclasses should be abstract

Abstract Classes

  • Any class with a pure virtual method

    • Since abstract class needs at least one pure method, if you don't have one, use the destructor

    • Virtual destructor MUST be IMPLEMENTED

  • subclasses of abstract classes are also abstract unless they implement all pure method

Concrete: Class that is not abstract Example of Concrete Class:

class Regular: public Student { // refer to Student class above
  public: 
    int fees() const override {
      return 700*numCourses
    }
}

Example of Abstract Class:

  • Rewrite Book hierarchy as Abstract:

UML:

Code:

class AbstractBook {
  string title, author;
  int length;
  protected:
    // prevents assignment through base class pointers from compiling, but implementation still available
    AbstractBook &operator=(const AbstractBook &other); 
  public:
    ...
   // Need at least 1 pure virtual method, so use destructors
    virtual ~AbstractBook() = 0;
};

class NormalBook: public AbstractBook {
    public:
      ...
      NormalBook &operator=(const BormalBook &other) {
        AbstractBook::operator=(other);
        return *this;
      }
      ~NormalBook() {}  // other classes similar
  }

Note: Virtual destructor MUST be implemented here as said before, even though it is pure virtual

AbstractBook::~AbstractBook() {}

Last updated