Embercode
✕
Object-Oriented ProgrammingLesson 4 of 17

55 min read

Polymorphism, virtual functions and abstraction

Why it matters

Polymorphism lets you write code against a general type, such as Shape, and have it behave correctly for each specific kind, such as Circle or Rectangle. The same call, area(), gives a different answer depending on the object it is called on. A drawing program can keep a list of shapes of many kinds and draw them all with one loop. A payroll system can pay every employee with one call. A compiler can evaluate every node of an expression tree with one function.

Polymorphism is the most-asked object-oriented topic in interviews, and it is the place where many candidates lose points. They know the word but not the mechanism. This lesson explains the mechanism: how a call is decided, what a virtual function really does in memory, why a base class needs a virtual destructor, and how an abstract class defines a contract. Each part comes with running code and the compiler messages you will see when it goes wrong.

The problem: the pointer decides the method

Start with the situation that polymorphism solves. Suppose you have a pointer to a base class, and it points at a derived object. Which version of a method should run?

Without any special keyword, C++ decides by the declared type of the pointer, not by the real object behind it. The compiler picks the function at compile time, looking only at Shape*, and it does not look at the object at all.

C++ · editable

Expected output:

shape

The pointer p is declared as Shape*, and it holds the address of a Circle. The call p->name() is resolved as a call to Shape::name. The Circle version exists, but the compiler never considers it through this pointer. The output says shape, which is wrong for the object, which is a circle.

This is not a bug in the compiler. It is a rule. Non-virtual functions are decided by the type of the expression that names them. For a plain class and a plain call, that is what you want, because it is fast and predictable. For a family of related classes, it is almost always the wrong rule, and the next section fixes it.

Virtual functions: the right method at run time

Mark the base method virtual. Now C++ looks at the real object at run time, and it calls the version that belongs to that object's class. This is runtime polymorphism, the same call giving different behaviour depending on the object at run time.

Think of a remote control with a "play" button. The button sends the same signal to every device. A television starts a film, a music speaker starts a playlist, and a game console starts a game. The remote does not need to know which device is in the room. Each device answers the same signal in its own way. A virtual function works the same way: the caller sends the same message, and the object decides what it means.

Here is the shape example done properly, with a list of shapes of two kinds.

C++ · editable

Expected output:

10
12

The loop has one line of code, s->area(), and it works for both kinds of shape. For the rectangle, area gives 2 times 5, which is 10. For the circle, the approximation of pi used in this example is 3, so the area is 3 times 2 times 2, which is 12. The loop does not change when you add a triangle later. You write a new derived class with its own area, and the loop still works.

Look at the two habits in the code. The derived classes write override after the method. This asks the compiler to check that the method really replaces a base virtual method, and it is covered in the next section. The base class has a virtual destructor, which we also come back to.

What a virtual call costs

Each object of a class with virtual functions holds a hidden pointer to a table of function addresses. The table is called the vtable, short for virtual table, and the hidden pointer is called the vptr. The table is made once per class, not once per object, so every Circle object points at the same Circle table. A virtual call looks up the right address in that table and jumps to it.

A Shape pointer leads to a Circle object, whose hidden pointer leads to the Circle vtable, which holds the address of Circle::areaA Shape pointer leads to a Circle object, whose hidden pointer leads to the Circle vtable, which holds the address of Circle::area

The lookup costs a little time. In most programs it does not matter. In a tight loop that runs billions of times, it can matter, and this is one reason C++ defaults to non-virtual functions. Use virtual when the design needs runtime choice, and not otherwise.

The vtable has another consequence. An object with virtual functions is a little bigger, because of the hidden pointer, and a class with no virtual functions has no hidden pointer at all. This is one more reason to make virtual a deliberate choice.

override and final

The keyword override is a promise that the method replaces a base virtual method. The compiler checks the promise. If the name is misspelt, if the parameters differ, or if the const is missing, the compiler reports an error instead of creating a new, unrelated method.

Here is the error from a missing const.

class Shape {
public:
    virtual double area() const { return 0; }
};
class Circle : public Shape {
public:
    double area() override { return 1; }   // missing const
};
int main() { Circle c; }
error: 'double Circle::area()' marked 'override', but does not override

Without override, the same code compiles silently. The Circle::area without const is a new function that does not override anything, so calls through a Shape pointer still run the base version and return 0. That is a silent wrong answer, the worst kind of bug, and override turns it into a compile error. Always write override in a derived class.

The keyword final does the opposite. A method marked final may not be overridden again, and a class marked final may not be derived from at all. Use final when a class is complete and any extension would break its design. In a payroll system, the final class Salaried might be used as is, and the compiler stops anyone from subclassing it by accident.

Virtual destructors

Here is the most important rule of polymorphism, and the one that is most often broken. A base class that is meant to be derived from must have a virtual destructor. If you delete a derived object through a base pointer, and the destructor is not virtual, only the base destructor runs. The derived part is never cleaned up, and the program has undefined behaviour.

The following program shows the problem. The base destructor is not virtual, and the program deletes a Box through a Shape pointer.

C++ · editable

Expected output on g++ 15:

box
~Shape

The ~Box message is missing. The call delete p looks at p's declared type, which is Shape*, and runs the non-virtual ~Shape. The derived destructor never runs, and anything Box owned would leak. This program only prints a message, so the damage is small. In a real program, the derived class might own a file or a block of memory, and it would be lost.

Compiling with -Wall shows the warning that the compiler gives for this situation. The warning is:

warning: deleting object of polymorphic class type 'Shape' which has non-virtual destructor might cause undefined behavior [-Wdelete-non-virtual-dtor]

Read it as a warning you must not ignore. The fix is one word: make the base destructor virtual.

class Shape {
public:
    virtual void draw() const { cout << "shape\n"; }
    virtual ~Shape() { cout << "~Shape\n"; }
};

With this change, delete p calls ~Box first, then ~Shape, and the output is box, ~Box, ~Shape. The rule has no exceptions in ordinary code. If a class has any virtual function, it should have a virtual destructor. If you want to forbid deletion through the base, make the destructor protected, so only the derived classes can run it. That is an advanced choice and you will rarely need it.

Abstract classes and pure virtual functions

Sometimes the base class is only a description. A Shape in general has an area, but there is no such thing as a plain shape with no kind. In that case the base method has no sensible body. C++ lets you say so with a pure virtual function, which is a virtual function that ends in = 0 instead of a body. A class with at least one pure virtual function is an abstract class, and you cannot create an object of it.

Abstraction means showing what something does, not how it does it. You press a car's pedal without knowing the engine. An abstract class shows the operations and hides the details, which the derived classes provide. Users of the abstract class depend only on the operations, so the details can change without affecting them.

C++ · editable

Expected output:

7
<<7>>

The function show takes a const Printer&. It knows nothing about Plain or Fancy, and it does not need to. It calls print, and the object decides how to print. If you add a Bold printer next year, show does not change. This is the main benefit of the design: code depends on the abstract contract, and new kinds plug in without edits.

A class that contains only pure virtual functions and no data is often called an interface. Other languages have a keyword for this, but C++ uses an abstract class with pure virtual functions. An interface says "anything that claims to be a Printer must provide print". The show function is written against that promise.

You cannot create a Printer object, because the class is abstract:

Printer p;   // error: cannot declare variable 'p' to be of abstract type 'Printer'

A derived class must provide a body for every pure virtual function, or it is abstract too. If Fancy forgot to write print, then Fancy would be abstract, and Fancy b; would fail with the same kind of message.

Worked example: an expression tree

Now the power of polymorphism shows. An expression tree is a tree of nodes that represents a calculation. Leaves are numbers. Inner nodes are operations, such as addition or multiplication, with two children. The expression (2 + 3) * 4 has a multiplication node at the top, an addition node as its left child, and the numbers 2, 3 and 4 as leaves.

Evaluating the tree means asking each node for its value. A leaf returns its number. An inner node asks its two children and combines the answers. Each kind of node does a different job, but the caller uses one operation, eval, on the root. That is polymorphism at work, and it is also a tree walk, which the visualizer below shows.

The following program builds the tree and prints both the text of the expression and its value. It uses unique_ptr, which deletes the node it owns when the owner goes away. That keeps the example short and free of leaks, and lesson 5 explains how the smart pointer does its job.

C++ · editable

Expected output:

((2 + 3) * 4) = 20

Trace eval on the root. The root is a Mul, so its eval asks its left child for a value. The left child is an Add, so its eval asks its left child, the number 2, which returns 2, and its right child, the number 3, which returns 3. The Add returns 5. Back in Mul, the right child is the number 4, which returns 4. The Mul returns 5 times 4, which is 20. The root's caller never needed to know that the root was a multiplication. It called eval, and the tree did the rest.

The text method works the same way, building the string from the children. That is why the output shows the nested brackets: the outer pair comes from the root, and the inner pair comes from the addition.

831016144
output: (none yet)

Numbers are inserted into the tree one by one: smaller goes left, larger goes right. A filled node is being visited. An outlined node is on the path being walked. A dark border means already visited.

inorder: visit left, then node, then right.

Step 1 of 16

The visualizer above walks a tree from the top down, and it shows the same kind of recursion. Each call goes to a child, waits for the answer, and then combines the results. Notice that the answer for a node is ready only after both of its children have answered. That is the same order eval uses.

Notice also the virtual ~Expr() {} line. The tree owns its nodes through unique_ptr<Expr>, so the owner deletes a node through a base pointer. If the destructor were not virtual, the derived parts of the nodes would not be cleaned up. In this example they own nothing, so nothing would leak, but the rule holds anyway, and the habit matters once a node owns memory.

Why this design scales

Suppose you need a new operation, such as a subtraction node. You write one new class, Sub, with its own eval and text. The existing eval calls and the text calls work without any change. This is the advantage of virtual functions for a family of types: adding a new kind is easy. The opposite is harder. Adding a new operation to all the kinds, such as simplify, means touching every class. Designers call this trade-off the expression problem, and a good answer in an interview names it.

Worked example: a payroll with an abstract employee

A second example uses an abstract base for employees. The base class says every employee has a name and a way to compute pay. Each kind of employee computes pay in its own way. The main program builds a list of employees of different kinds and pays them all with one loop.

C++ · editable

Expected output:

Anil: 5000
Bina: 800
Chetan: 150
total: 5950

Trace the loop for Bina. The loop variable e is a unique_ptr<Employee>, and e->pay() calls pay on the real Hourly object. The Hourly version returns 20 times 40, which is 800. The loop prints Bina: 800 and adds 800 to the total. Anil uses the Salaried version and Chetan uses the Hourly version again, with different numbers. The one loop handles three kinds of employee correctly.

The vector stores unique_ptr values, so the employees are deleted when the vector goes away, and the virtual destructor makes sure each derived part is cleaned up. The loop takes the employee by const auto&, which avoids copying the pointer and makes clear that the loop does not change the employee.

Notice what the abstract Employee promises. Any new kind of employee, such as a Contractor, must write its own pay. The compiler enforces that, because a class that does not write pay stays abstract, and make_unique<Contractor> would fail to compile. The contract is checked at compile time.

What happens when a virtual function is called from a constructor

Here is a subtle rule that interviewers like. While a base constructor runs, the object is still a base object. The derived part does not exist yet, so a virtual call made from the base constructor runs the base version, not the derived one. The same is true for the destructor: once the derived part has been destroyed, the object is a base object again.

C++ · editable

Expected output:

Base sees: base
Object says: derived

The constructor of Base runs before the Derived part exists. So the virtual call inside it runs the Base version, and it prints base. After the object is complete, the call from main runs the Derived version. The rule is simple: do not depend on a derived override inside a constructor or a destructor. Keep those functions simple and avoid virtual calls there.

Trace one virtual call, step by step

The idea is easier to hold when you follow one call through every step. Take the Printer example from this lesson, and look at what happens when show is called with a Fancy object.

void show(const Printer& p) {
    p.print(7);
}

Here is the sequence, written as the work the program does at run time.

StepWhat happensWhere the answer comes from
1main passes the Fancy object to show by reference. No copy is made, so p is another name for the object.The object itself
2show calls p.print(7). The compiler sees that print is virtual, so it does not pick a function yet.The compiler's rule for virtual calls
3The program reads the hidden vptr stored inside the object. It leads to the vtable of the Fancy class.The object's own pointer
4The program finds the entry for print in that vtable. The entry holds the address of Fancy::print.The vtable of Fancy
5The program jumps to Fancy::print, passing the object's address as this. The function prints <<7>>.The address in the vtable

Notice that show never mentioned Fancy. The choice was made in steps 3 and 4, from the object that really exists at run time. If you call the same function with a Plain object, the same steps run, and the vtable of Plain leads to Plain::print. The code in show is the same, and only the object changes.

This also shows why the reference matters. If show took a Printer by value, step 1 would make a copy of only the Printer part, and the vptr in the copy would point at the Printer vtable. Step 5 would then run the base version, which is the slicing problem from lesson 3 seen from the inside.

Designing an interface for a family of types

An interface is the list of operations that a family of types promises to provide. Designing one well is one of the most useful skills in object-oriented code. The steps below apply whether you are writing a payroll system, a drawing program or a set of storage backends.

Step 1: list what callers need to do. Write the sentences a caller would say. "Pay this employee." "Ask this employee for a name." Each sentence becomes a candidate method. If a method is only needed by one kind of employee, it does not belong in the shared interface.

Step 2: keep the data out of the interface. The interface should describe behaviour, not storage. A pay method says what the employee earns. It does not say whether the employee's rate is stored as an hourly rate or a yearly salary. Storage is a detail of each derived class.

Step 3: make each operation pure virtual, or give it a sensible default. A pure virtual function forces every kind to provide the operation. A virtual function with a default body lets a kind skip it when the default is right. Use pure virtual for operations that have no common meaning.

Step 4: make the read-only operations const. A caller who holds a const reference to the interface can then ask for information without changing anything. Operations that change the object stay non-const.

Step 5: give the interface a virtual destructor. Callers will hold the objects through the interface, so they will delete them through it too.

Step 6: write a function against the interface, and test it with two kinds. A payroll function that takes const Employee& and calls pay is the test. If the function works unchanged for a new kind, the design is right. If the function needs to ask which kind it has, the interface is missing an operation.

A good interface is small. Each method should be something a caller needs, and each should have a clear result. Callers depend on the interface, so every method you add is a promise you must keep when the derived classes change. A large interface with many methods that only a few kinds use is a sign that the design should be split.

Asking which kind an object is, with a chain of checks, is a warning sign. It usually means the function should have been a virtual method, so that the object answers for itself. The opposite warning is a method that every kind overrides with the same body. That method probably belongs in the base class as a normal method, not as a virtual one.

Common mistakes with real compiler messages

Mistake 1: a missing override is silently a new function. Without override, a misspelt name is not an error, because the derived method is simply a new method. The fix is to write override and let the compiler check. The message from a const mismatch was shown above:

error: 'double Circle::area()' marked 'override', but does not override

Mistake 2: an abstract class used by value. The abstract class cannot be created, and it also cannot be passed by value into a function, because the function would need to create a copy. The compiler says so directly:

class Shape {
public:
    virtual double area() const = 0;
};
void print(Shape s) {}
error: cannot declare parameter 's' to be of abstract type 'Shape'

The fix is to pass by reference, void print(const Shape& s), which is what the polymorphism examples do.

Mistake 3: an abstract object declared as a local variable. The same rule applies to a plain declaration in main, such as Shape s;. The message is cannot declare variable 's' to be of abstract type 'Shape', and the fix is to create a derived object instead.

Mistake 4: no virtual destructor. This is the mistake with a warning. Compile with -Wall and read the warning:

warning: deleting object of polymorphic class type 'Shape' which has non-virtual destructor might cause undefined behavior [-Wdelete-non-virtual-dtor]

When you see it, add virtual to the destructor of the base. The derived part is then cleaned up, and the warning disappears.

Mistake 5: a virtual function that is not virtual on the base. If the base method has no virtual keyword, a derived method with the same name hides it, as lesson 3 showed. Only the pointer's type decides the call. The fix is to add virtual to the base method, and to write override in the derived one, so the compiler checks both.

Mistake 6: slicing an object of an abstract hierarchy. Passing a derived object by value into a base parameter loses the derived part, and with it the virtual behaviour. It is covered in lesson 3. The rule for polymorphism is the same: pass by reference or pointer, and never by value.

Debugging walkthrough: the wrong method runs

Suppose the program prints shape for a circle, even though Circle has its own describe. Here is the search.

  1. Find the call that gives the wrong answer. Write the expression down, and note the declared type of the object that receives the call. Suppose it is Shape* p and the call is p->describe().
  2. Check the base declaration. Is describe marked virtual in Shape? If not, the call is decided by Shape*, so the base version runs. This is the most common cause.
  3. Check the derived declaration. Does Circle::describe have the same name, the same parameters and the same const? If not, it is a new method and not an override. Write override to find out. The compiler stops and tells you what is wrong.
  4. Check the object. Is the pointer really pointing at a Circle? A pointer can be wrong after a bad cast, or after an object has been deleted.
  5. Add a print in the derived method. If it never runs, the call is not reaching it, and the cause is steps 2 or 3. If it runs, the problem lies elsewhere.

The overall habit is to make the compiler do the checking. Write override on every derived virtual method, enable the warnings with -Wall -Wextra, and read them. Most polymorphism bugs are caught this way before the program ever runs.

Variations and edge cases

Pure virtual with a body. A pure virtual function may still have a body, and a derived class may call it with Base::method(). This is useful when most derived classes share a default and need the base's code as a helper. It is uncommon, and you should mention it only when asked.

Covariant return types. A derived method may return a more specific type than the base method, if the two types are related by inheritance. For example, a clone() method may return Circle* in Circle when the base returns Shape*. Interviews sometimes ask about it.

Virtual calls through references. A reference works like a pointer for polymorphism. void show(const Shape& s) calls the derived version when s refers to a Circle.

Multiple inheritance of interfaces. A class may inherit from several abstract classes, each with pure virtual functions. This is a common and safe use of multiple inheritance, because interfaces hold no data, so there is no duplicated base part to manage.

Performance. A virtual call is slightly slower than a normal call, because it reads the vtable. For most code the difference does not matter. In a hot loop you can avoid it by working on values or by using templates, which are compile-time polymorphism. This is a topic for later.

Interview questions and how to answer them

What is polymorphism? The ability to call the same function on different types and get the behaviour of the actual type, at run time. In C++ it is achieved with virtual functions through a base pointer or reference.

Why does a base class need a virtual destructor? So that deleting a derived object through a base pointer runs the derived destructor as well. Without it, the derived part leaks, and the program has undefined behaviour.

What is an abstract class? A class with at least one pure virtual function. You cannot create objects of it, and it defines a contract that derived classes must fulfil.

What does override do? It asks the compiler to check that the method really overrides a base virtual method, so a typo or a missing const becomes an error.

What is a vtable? A table of function addresses for a class with virtual functions. Each object holds a hidden pointer to its class's table, and a virtual call looks up the function there.

What runs when a virtual function is called from a constructor? The version in the class being constructed, because the derived part does not exist yet.

Real-world uses

Polymorphism is the way many frameworks let you plug in your own code. A web server calls handle on each request handler, and your class overrides it. A user interface calls paint on each widget, and every widget paints itself. A database driver offers one abstract Connection class with a different implementation for each database, so the application code does not change when the database does.

It is also how the standard library lets you customise behaviour, although templates do more of that work in modern C++. Exceptions, streams and many allocators use virtual functions in their designs. When you read a library and see an abstract class with = 0 methods, you are looking at the contract the library expects you to implement.

Practice

Try each exercise before you open the solution. Use Run to check your answer.

  1. Add a Triangle class to the shape example that stores a base and a height and returns half of base times height. Add it to the vector and check that its area prints. Hint: the integer division is avoided if you use double for the members.
Solution
class Triangle : public Shape {
    double base, height;
public:
    Triangle(double b, double h) : base(b), height(h) {}
    double area() const override { return base * height / 2; }
};

Add new Triangle(4, 5) to the vector. Its area is 4 times 5 divided by 2, which is 10. The loop does not change.

  1. In the shape example, remove virtual from area in the base class and predict what the program does. Hint: look at the override keywords in the derived classes, and remember that the compiler checks them.
Solution

The program does not compile. Without virtual in the base, the derived area methods marked override replace nothing, and the compiler reports marked 'override', but does not override. If you also delete the override keywords, the program compiles and prints 0 and then 0, because s->area() is decided by the pointer type Shape* and the base version returns 0.

  1. Write an abstract Employee with a pure virtual salary() and two derived classes, Manager and Engineer. Give each one a constructor with a name and a base amount. The manager gets the base plus 1000. Print the salary of one of each. Expected for a manager with base 5000 and an engineer with base 4000: 6000 and 4000.
Solution
#include <iostream>
#include <string>
using namespace std;
 
class Employee {
protected:
    string name;
public:
    Employee(string n) : name(n) {}
    virtual int salary() const = 0;
    virtual ~Employee() {}
};
 
class Manager : public Employee {
    int base;
public:
    Manager(string n, int b) : Employee(n), base(b) {}
    int salary() const override { return base + 1000; }
};
 
class Engineer : public Employee {
    int base;
public:
    Engineer(string n, int b) : Employee(n), base(b) {}
    int salary() const override { return base; }
};
 
int main() {
    Manager m("Asha", 5000);
    Engineer e("Ravi", 4000);
    cout << m.salary() << "\n" << e.salary() << "\n";
    return 0;
}

The output is 6000 and then 4000.

  1. Explain in two sentences why Employee in the previous exercise cannot be created as an object.
Solution

Employee has a pure virtual function, salary() = 0, which has no body. A class with a pure virtual function is abstract, and C++ does not allow an object of an abstract class, because there would be no code for the missing function.

  1. Write a Base class with a virtual function hello() that prints base hello, and a Derived class that overrides it to print derived hello. Call hello through a Base& that refers to a Derived object. Expected: derived hello.
Solution
#include <iostream>
using namespace std;
 
class Base {
public:
    virtual void hello() const { cout << "base hello\n"; }
    virtual ~Base() {}
};
 
class Derived : public Base {
public:
    void hello() const override { cout << "derived hello\n"; }
};
 
void call(const Base& b) {
    b.hello();
}
 
int main() {
    Derived d;
    call(d);
    return 0;
}

The reference refers to a Derived object, so the derived version runs.

  1. Suppose Shape is not abstract, and its area() returns 0. Explain why vector<Shape> v; v.push_back(Circle(2)); is a bug even though it compiles. Hint: think about slicing.
Solution

The vector stores Shape objects, so push_back copies only the Shape part of the Circle. The circle's data is lost, and calling area() on the element gives the base answer, 0. The fix is to store pointers or smart pointers, such as vector<unique_ptr<Shape>>, so each element keeps its real type.

  1. Write a Clock abstract class with a pure virtual show() and a concrete DigitalClock that prints 12:00. Create a DigitalClock through a unique_ptr<Clock> and call show. Expected output: 12:00.
Solution
#include <iostream>
#include <memory>
using namespace std;
 
class Clock {
public:
    virtual void show() const = 0;
    virtual ~Clock() {}
};
 
class DigitalClock : public Clock {
public:
    void show() const override { cout << "12:00\n"; }
};
 
int main() {
    unique_ptr<Clock> c = make_unique<DigitalClock>();
    c->show();
    return 0;
}

The output is 12:00. The owner is a unique_ptr<Clock>, so the virtual destructor deletes the right object.

  1. The following code does not print what its author expects. Explain why, and fix it.
class Base {
public:
    virtual void go() { cout << "base\n"; }
};
class Child : public Base {
public:
    void go(int x) { cout << "child\n"; }
};
int main() {
    Base* p = new Child();
    p->go();
}

Hint: the name go in Child takes a parameter, so it is not an override.

Solution

Child::go(int) has a different parameter list, so it is a new function, not an override. Calls made through a Child object would find it, but the call through Base* can only see Base::go, which prints base. The fix is to give Child a method void go() override with no parameters, so the compiler checks that it really overrides Base::go.

  1. Explain in one or two sentences what the vtable is and where its pointer lives.
Solution

The vtable is a table of function addresses that is made once for each class with virtual functions. Each object of that class holds a hidden pointer, called the vptr, to the table of its class, and a virtual call looks up the right function there.

  1. Explain why calling a virtual function from a constructor does not run the derived version, using the words "derived part".
Solution

While the base constructor runs, the derived part of the object has not been built yet. The object is still a base object, so a virtual call from the base constructor uses the base version. The derived version can run only after the derived part exists.

What you now know

  • A non-virtual call is decided by the declared type of the pointer or reference. A virtual call is decided by the real object at run time.
  • A virtual function is called through a vtable, which is a table of function addresses that each object reaches through its hidden vptr.
  • Write override on every derived virtual method, so the compiler checks the signature.
  • A base class with virtual functions needs a virtual destructor, or deleting through a base pointer skips the derived cleanup.
  • A pure virtual function (= 0) makes a class abstract. Abstract classes cannot be created or passed by value, and they define a contract.
  • A virtual call made from a constructor or destructor runs the version of the class being built or destroyed.
  • Next, lesson 5 covers static members and the rule of three, for classes that own memory and must copy safely.
Practice this topic: coding problems for Object-Oriented Programming →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Inheritance