Embercode
✕
Object-Oriented ProgrammingLesson 3 of 17

50 min read

Inheritance

Why it matters

Inheritance lets one class reuse and extend another. Instead of copying the code of a Vehicle into a Car, you write the shared parts once and let the Car take them over. Many real programs rely on this: a graphical user interface has a base Widget with many kinds of button, text box and slider built on top of it; a game has a base Entity with players, enemies and items; a compiler has a base Node with an expression node for each kind of operation.

Inheritance is also the foundation for polymorphism, the topic of lesson 4. Interviewers use it as a test of design. They ask you to model a small set of related things, and they watch whether you choose inheritance for the right reason, whether you set up constructors correctly, and whether you know what happens when an object is copied into a base-class variable. This lesson covers all of that, with running code.

The idea: an is-a relationship

Inheritance creates a new class, called the derived class or child class, that takes on the members of an existing class, called the base class or parent class. The derived class gets everything the base class has, and it can add its own members.

The key question is whether the relationship is "is-a" or "has-a". Use inheritance only for "is-a". A dog is an animal, so Dog may inherit from Animal. A car has an engine, so a Car should hold an Engine as a member, not inherit from it. If you inherit from Engine to get the engine's code, every car becomes an engine, which is wrong. Programmers call the second design composition, and lesson 5 touches on it again.

A simple test helps. Say the sentence "X is a kind of Y" out loud. "A dog is a kind of animal" sounds right. "A car is a kind of engine" sounds wrong, so the car holds an engine. "A sports car is a kind of car" sounds right, so SportsCar inherits from Car.

Think of a family tree. A child inherits features from the parent: eye colour, a surname, a family recipe. The child can also develop new features the parent never had. In C++ the base class is the parent, and the derived class is the child. The child starts with the parent's features and adds its own.

Writing a derived class

Here is the syntax. The derived class name is followed by a colon, an access level and the base class name. The access level here is public, which is the normal choice for an is-a relationship.

C++ · editable

Expected output:

Animal(Rex)
Dog(Rex)
Rex eats
Rex barks

Trace the program from the top of main. Dog d("Rex"); starts the creation of a Dog. Before the Dog body can run, the Animal part of the object must exist, so the Animal constructor runs first, with the argument "Rex". It sets name and prints Animal(Rex). Then the Dog constructor body runs and prints Dog(Rex). Notice that the Dog constructor can use name directly, because name is protected in Animal.

Next, d.eat() runs. Dog does not define eat, but it inherits it, so the call finds the method in Animal. The name inside eat is still "Rex". Last, d.bark() runs the method that Dog defines.

Three details to notice in the syntax:

  • class Dog : public Animal names the base class and says that the public parts of Animal stay public in Dog.
  • Dog(string n) : Animal(n) calls the base constructor in the initialiser list. This is required when the base has no default constructor. The derived constructor must pass what the base needs.
  • name is a member of the base, but Dog writes it without any prefix. A derived class sees the base's protected and public members as its own.

What happens inside: the base part sits inside the derived object

Here is what the object looks like in memory. A Dog object is one block of memory. Inside that block sits a smaller block for the Animal part, holding name. After it come the members that only Dog declares. When the code converts a Dog to an Animal, the pointer or reference simply points at the first part of the block, which is the Animal part.

A Dog object drawn as one block: the Animal part holding name comes first, followed by the Dog-only membersA Dog object drawn as one block: the Animal part holding name comes first, followed by the Dog-only members

This layout explains several rules that look strange at first. A derived class can read base members because the base part is physically there. A base-class pointer can point at a derived object because the base part sits at the start of the object. And the destructors must run in the reverse order of construction, because the derived part was built on top of the base part and must be taken away first.

Notice that the picture shows a single base class. Inheritance from several base classes, called multiple inheritance, places several base parts inside one object. C++ supports it, but beginners should avoid it until they are comfortable with single inheritance.

Constructor and destructor order

The base part must exist before the derived part is built. So constructors run base first, then derived. Destructors run in the opposite order: derived first, then base. This follows directly from the layout above. The derived body may use base members, so the base must be ready. The base may not outlive the derived part, so the derived part is destroyed first.

The next program shows a chain of three classes, so you can see the order at each level. A SportsCar is a Car, and a Car is a Vehicle. Each class prints when it is built and when it is removed.

C++ · editable

Expected output:

Vehicle built
Car built
SportsCar built
4 wheels, 2 doors, 250 km/h
SportsCar removed
Car removed
Vehicle removed

Follow the chain. SportsCar s(250) calls the SportsCar constructor. Its initialiser list says Car(2), so before the SportsCar body runs, the Car constructor must run, with the argument 2. The Car constructor's initialiser list says Vehicle(4), so the Vehicle constructor must run first, with the argument 4. So the work goes down the chain: SportsCar asks Car, Car asks Vehicle. Then the bodies run in the other direction, from the base upward: Vehicle built, then Car built, then SportsCar built.

The show method prints 4 wheels, 2 doors, 250 km/h. The number 4 comes from the Vehicle part, which is two levels up. The derived class reads it as if it were its own.

When main returns, s is destroyed. The SportsCar destructor runs first, then the Car destructor, then the Vehicle destructor.

The top of the stack is the first box. The call on top is the one running. A filled box is returning a value.

Call factorial(4). It goes on top of the stack.

Step 1 of 9

The visualizer above shows the same kind of chain as a stack of calls. Each constructor waits for the one below it to finish, and the bottom of the stack finishes first. Watch how the frames go up and then come back down. The shape is the same as a recursive function, which is why the order is easy to predict once you see it.

Passing arguments up the chain

Notice that the SportsCar constructor does not set wheels or doors itself. It passes the arguments it needs to Car, which passes them to Vehicle. Each level sets only the data it owns. This is a good design rule: a class initialises its own members and asks its base class to initialise the base part. If a base class has a constructor with arguments, every derived class must pass those arguments, or the code does not compile.

If the base has a default constructor, you may leave the base out of the initialiser list, and the default one runs. If the base has no default constructor, leaving it out is a compile error.

Worked example: a person and a student

Here is a second example with constructor arguments at two levels, and a method in the base that the derived class uses. It models a person and a student who is also a person.

C++ · editable

Expected output:

Hello, I am Meera
Hello, I am Kiran
I am in year 2

The Student class defines its own introduce. Inside it, Person::introduce() calls the base version explicitly. The double colon names the class whose version to run. The student's version then adds a second line. This pattern, calling the base version and then adding to it, is very common. It avoids copying the base code into the derived class, so a change to Person takes effect in every student too.

The student's constructor takes two arguments and passes the first one to Person. The second one, year, is stored by the Student part itself. Each class sets what it owns.

Access levels in an inherited class

A derived class can see the base class's members according to their access labels. Here is the table you need.

Member in the base classSeen by the derived classSeen by code outside
publicyesyes
protectedyesno
privatenono

A private member of the base is hidden even from the derived class. The derived class can only use it through the base's public or protected methods. That is why the Student class above can read name (protected) but cannot read a private member of Person directly.

The access level also has a second meaning, which is the inheritance access. It is written before the base class name and controls how the base's members look from the outside of the derived class. There are three choices.

  • public inheritance: public stays public and protected stays protected. This is the normal choice. A Dog is an Animal in every way.
  • protected inheritance: everything from the base becomes protected in the derived class. Outside code cannot convert a Dog to an Animal. This is rare.
  • private inheritance: everything from the base becomes private in the derived class. Outside code can no longer see the base's interface. This is rarer still, and it is used to reuse code without claiming an is-a relationship.

The default inheritance access depends on the keyword. For class Dog : Animal it is private. For struct Dog : Animal it is public. This is a common trap, which the mistakes section shows.

Overriding and hiding a method

A derived class can define a method with the same name and parameters as a base method. The new method hides the base version. Calls on the derived object use the new version.

C++ · editable

Expected output:

a circle
a shape

c.describe() finds the Circle version first, because the search for a name starts in the most derived class. c.Shape::describe() names the base version explicitly, so it runs the hidden one. The double colon tells the compiler which class's version to use.

Hiding is not the same as overriding. Overriding means the derived version replaces the base version even when the object is seen through a base pointer or reference. Hiding means the derived version is chosen only when the static type of the expression is the derived class. Here the object is a Circle in main, so the Circle version is chosen. Lesson 4 shows what happens through a base pointer, and why virtual is needed to get overriding.

Hiding has a hidden trap. If a derived class declares a method with the same name as a base method but different parameters, the base versions are hidden too, even the ones with matching parameters. The fix is a using declaration, such as using Base::method;, which brings the base versions back into the derived class. You will rarely need it, but it appears in interview questions about name hiding.

Protected data in a bank account

Inheritance is often used to add behaviour to a class that already protects its data. Here the base Account keeps its balance private to the family, and a Savings account adds interest. The balance is protected, so only Account and classes derived from it can touch it, and outside code cannot.

C++ · editable

Expected output:

1050
1102

Trace it. The Savings account starts with a balance of 0. deposit(1000) is inherited from Account, so the balance becomes 1000. addInterest adds 5 percent: 1000 times 5 is 5000, divided by 100 is 50, so the balance becomes 1050. The second call adds 5 percent of 1050, which is 52 with integer division, so the balance becomes 1102. The output has two lines because the program prints after each call.

Notice what Savings can do and what it cannot. It can write balance directly, because balance is protected. But if balance had been private, addInterest would have to call a public method of Account to change it, and the base class would keep its rule in one place. In a real project, a protected member is a promise to the derived classes, so changing it can break them. Many teams prefer protected methods to protected data for that reason.

Slicing: copying a derived object into a base object

Slicing is one of the most important traps of inheritance. It happens when a derived object is copied into a base-class object by value. Only the base part is copied. The derived part is cut off, which is where the name comes from, like a slice cut from a loaf.

C++ · editable

Expected output:

Rex says woof
Rex says ...

The sound method is marked virtual, and the words override and virtual will be explained in lesson 4. For now, watch what happens to the object. byReference receives a reference to the real Dog, so the call to sound runs the Dog version and prints woof. byValue receives a copy. The copy is an Animal object, and only the Animal part is copied. The Dog part is gone. Inside byValue, the object is an Animal, so sound returns the base version, ....

Slicing is silent. The program compiles, it runs, and it prints a wrong answer. That is why the habit of passing objects of a class hierarchy by reference or pointer matters so much. The rule is simple: when a function takes a base class, take it as const Base& or as a pointer, never by value.

A checklist for writing a derived class

Derived classes go wrong in a small set of predictable ways. Before you finish a derived class, go through this list. Each item is a question you can answer by reading your own code.

  • The inheritance says public unless you have a specific reason for another access level.
  • The constructor passes every argument the base constructor needs, in the initialiser list.
  • The derived class sets only its own members, and leaves base members to the base constructor.
  • Each protected member is one that derived classes truly need. Protected data is a promise to every future derived class.
  • A method that replaces a base method says what it does differently, and calls Base::method() when the base behaviour is part of the job.
  • No function takes a base object by value when it should take a reference, because that causes slicing.
  • If the base will be deleted through a pointer, its destructor is virtual.
  • The relationship passes the square and rectangle test: every promise of the base still holds for the derived class.

Two of these items deserve a longer look. The first is the protected data question. Protected members let derived classes change the base's data directly, so any change to the base's data layout can break every derived class. A safer design gives the base protected methods, which the derived classes call, and keeps the data private. The base then keeps its rules in one place, and the derived classes cannot break them by mistake.

The second is the calling of base methods. A derived method that replaces a base method often needs the base work too, such as checking a rule or printing a header. Calling Base::method() at the start or end of the derived version keeps that work in one place. Forgetting the call is a silent bug, because the program still runs, but the base part of the job is skipped.

Name lookup inside a derived class

When you write a name inside a method, the compiler searches for it in a fixed order. First it looks in the current block, then in the function's own parameters and locals, then in the class, then in the base classes, and last in the namespace. The first match wins, and the search stops there. Knowing this order explains most of the surprises that come with hiding.

Here is a small case. The base class has a member called label, and the derived class declares its own member with the same name.

class Base {
protected:
    string label = "base label";
};
class Derived : public Base {
    string label = "derived label";
public:
    void show() const {
        cout << label << "\n";        // finds Derived::label first
        cout << Base::label << "\n";  // names the base member explicitly
    }
};

Inside show, the plain name label finds the derived member first, so the program prints derived label. The qualified name Base::label skips the search and goes straight to the base member, so the second line prints base label. Having two members with the same name is legal, but it confuses readers, so choose different names unless the shadowing is the whole point.

The same search order applies to methods. If a derived class declares a method called show with one set of parameters, every base show is hidden, as lesson 3 showed earlier. A using declaration brings the base versions back into the search, and it reads like this: using Base::show; inside the derived class. Use it when you add an overload to a class whose base already has useful overloads, so that callers can still reach them through the derived class.

Choosing between inheritance and composition

Inheritance and composition both reuse code, but they make different promises. Inheritance says that the derived class is a kind of the base, and it gives the derived class the base's whole public interface. Composition says that the class has a part of another type, and it keeps that part hidden behind the class's own methods. Choosing the wrong one makes the design hard to change later, so it is worth testing the choice with concrete questions before you write the class.

Take a stack that needs a list inside it. You could write class Stack : public List, which would make every list method available to anyone who holds a stack. A caller could then insert a value in the middle, and the stack's rule, that only the top can change, would be broken. Composition avoids this. The stack holds a list as a private member and offers only push and pop. Callers cannot reach the list, so the rule holds.

Now the classic trap. Suppose you think that a square is a kind of rectangle, so you write class Square : public Rectangle. The rectangle has setWidth and setHeight, and the square must keep both sides equal, so it overrides both methods to set both sides. Then this function is written against the rectangle:

void stretch(Rectangle& r) {
    r.setWidth(5);
    r.setHeight(10);
    // the caller expects the area to be 50
}

With a real rectangle the area is 50. With a square, the second call changes the width too, so the area is 100. The function's author wrote code that is correct for every rectangle, and the square breaks the promise that the base made. This is the Liskov substitution principle, a rule that says a derived object must be usable anywhere the base is used, without surprises. When a derived class needs to ignore or change a base promise, the is-a relationship is probably wrong.

Three questions help you decide.

  1. Will callers of the derived class ever need base methods that do not fit the derived class? If yes, use composition.
  2. Can every use of the base class be replaced by the derived class without a surprise? If not, the relationship is wrong.
  3. Does the relationship always hold, or only in one part of the program? If only sometimes, use composition or a separate class.

Composition is often the safer default. It costs a little more code, because you write forwarding methods, but each method you forward is a decision you made on purpose. Inheritance is the right tool when the is-a relationship is clear, stable, and needed for virtual behaviour, as the shape and employee examples in lesson 4 show.

Common mistakes with real compiler messages

Mistake 1: the base constructor needs arguments you did not pass. If Animal has only a constructor that takes a name, every Dog must pass a name to it. Forgetting that gives the error from lesson 2, now with a base class in the message.

class Animal {
public:
    Animal(string n) {}
};
class Dog : public Animal {
public:
    Dog() {}
};
int main() { Dog d; }
error: no matching function for call to 'Animal::Animal()'

The compiler tries to build the Animal part with no arguments, because the Dog constructor did not say how. Add : Animal("generic") to the Dog constructor, or give Animal a default constructor.

Mistake 2: forgetting public in the inheritance. This is the trap of the default private inheritance for class.

class Animal {
public:
    void eat() {}
};
class Dog : Animal {
};
int main() {
    Dog d;
    Animal* a = &d;
}
error: 'Animal' is an inaccessible base of 'Dog'

Because Dog inherits privately by default, the Animal part is hidden from the outside, and the conversion from Dog* to Animal* is refused. Write class Dog : public Animal to fix it. Read the message carefully: "inaccessible base" is the clue that the base is visible inside the class but not outside it.

Mistake 3: deriving from a class marked final. The keyword final (C++11) forbids further inheritance. A class that a library author marked final cannot be extended.

class Vehicle final {
public:
    int wheels = 4;
};
class Car : public Vehicle {
};
error: cannot derive from 'final' base 'Vehicle' in derived type 'Car'

Use final on purpose when a class is not designed for extension. Remove it if you need the extension.

Mistake 4: slicing without noticing. As shown above, copying into a base object by value loses the derived part. There is no compiler message, so the mistake shows up only as wrong output. The fix is to pass by reference.

Mistake 5: forgetting the base version in an override. When a derived class overrides a method and needs the base behaviour too, it must call the base version explicitly with Base::method(). If it does not, the base part is never run. For example, a Savings::describe() that never calls Account::describe() will skip any work the base does in that method.

Debugging walkthrough: a constructor message appears in the wrong place

Suppose you write the vehicle program and a student says the output is in the wrong order: "the car is built before the vehicle". Here is how to check.

  1. Re-read the constructor chain. The SportsCar initialiser list calls Car(2), and Car calls Vehicle(4). So the order must be Vehicle, Car, SportsCar.
  2. Look for a print statement that is in the wrong place. If Car printed before its base finished, the Car body ran before the Vehicle constructor. That cannot happen in C++, so the likely cause is that the print was placed in the wrong function, perhaps in an initialiser list rather than the body.
  3. Add a print of this to the constructors. Printing the address shows which object is being built, which helps when you have two objects of the same class.
  4. Test a smaller case. Create only a Vehicle, then only a Car, and compare the output. Small tests make the order easy to read.

The student in this story was right to ask, but the order was correct. The confusion came from reading the Car body as if it ran first. Trace the chain, and the order is clear.

Variations and edge cases

Multi-level inheritance is a chain of three or more classes, as in the vehicle example. Each class must pass its own arguments up the chain. A long chain is a sign that the design needs review, because the bottom class depends on every class above it.

The final keyword on a method. A virtual method may be marked final to stop derived classes from overriding it. You will see it in lesson 4, where it matters more.

Inheriting constructors. A derived class does not automatically inherit the base constructors. If you want them, write using Base::Base; inside the derived class. This is a convenience, and it can surprise readers, so use it sparingly.

An empty base class. A base class with no data members is common when you only want to share methods or to mark a type. Such an object still has a small size, but the base part adds no data.

Diamond problem. If two base classes share a common base, and a derived class inherits from both, the object contains two copies of the common base unless the inheritance is declared virtual. This is the diamond problem. It is a classic interview question, and the usual answer is to avoid the situation by choosing composition instead.

Interview questions and how to answer them

What is the difference between is-a and has-a? Is-a is inheritance: a Dog is an Animal. Has-a is composition: a Car has an Engine as a member. Use inheritance only for is-a, because inheritance exposes the base's interface to every derived class.

In what order are constructors and destructors called? Base first, then derived, for construction. The reverse for destruction. Explain it with the layout: the base part sits inside the derived object and must exist first.

What is slicing and how do you prevent it? Slicing is copying a derived object into a base object by value, which loses the derived part. Pass by reference or pointer, and avoid storing base objects by value in containers.

What does class D : Base do differently from class D : public Base? The first inherits privately, so the base is inaccessible outside D. The second keeps the public interface visible. Mention the default for struct as well.

Why call the base constructor in the initialiser list? Because the base part must be built with the right arguments, and the derived constructor is the only place that knows what to pass.

Real-world uses

Inheritance is used in frameworks, where a base class defines the shape of a plug-in and the user writes derived classes. A web framework has a base Controller and each page class derives from it. A user interface library has a base Widget and each control extends it with its own drawing code. A game has a base Entity for anything that moves and is drawn, and each character type adds its own behaviour.

It is also used in the standard library. Many exceptions derive from a common base, so a handler can catch the base and deal with every kind. Stream classes derive from one another, so the same code can write to a file or to the console.

Use inheritance where the shared code is large and the is-a relationship is clear. For smaller sharing, composition is usually simpler and safer.

Practice

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

  1. Create Vehicle with a wheels member set by its constructor, and a Car class that derives from it and prints Car with N wheels in its constructor, where N comes from the base. Create a Car and check the order of messages. Hint: the base constructor must run first.
Solution
#include <iostream>
using namespace std;
 
class Vehicle {
protected:
    int wheels;
public:
    Vehicle(int w) : wheels(w) { cout << "Vehicle ready\n"; }
};
 
class Car : public Vehicle {
public:
    Car() : Vehicle(4) { cout << "Car with " << wheels << " wheels\n"; }
};
 
int main() {
    Car c;
    return 0;
}

The output is Vehicle ready and then Car with 4 wheels. The base is built first, so wheels has its value when the Car body runs.

  1. Add a Cat class that derives from Animal and has a meow() method. Give Animal a constructor with a name parameter, and have Cat pass the name up. Print the name from main through meow. Hint: meow can use name because it is protected.
Solution
#include <iostream>
#include <string>
using namespace std;
 
class Animal {
protected:
    string name;
public:
    Animal(string n) : name(n) {}
};
 
class Cat : public Animal {
public:
    Cat(string n) : Animal(n) {}
    void meow() const { cout << name << " says meow\n"; }
};
 
int main() {
    Cat c("Tom");
    c.meow();
    return 0;
}

The output is Tom says meow.

  1. Explain in two sentences why Dog can read name but main cannot.
Solution

name is protected in Animal. A derived class such as Dog may use protected members, but code outside the class hierarchy, such as main, may not.

  1. Predict the output of this program, then check it:
#include <iostream>
using namespace std;
class A {
public:
    A() { cout << "A "; }
    ~A() { cout << "~A "; }
};
class B : public A {
public:
    B() { cout << "B "; }
    ~B() { cout << "~B "; }
};
int main() {
    B b;
    cout << "end";
}

Expected: A B end~B ~A with no newline issues. Hint: the destructors run after main prints its text.

Solution

The output is A B end~B ~A . The base constructor runs first and prints A , then B . main prints end. When main returns, the destructors run: ~B first, then ~A . The output has no newline, so the text runs together.

  1. A class Shape has a method describe() that prints shape. A class Square derives from it and defines describe() that prints square. Write main that calls describe on a Square and then calls the base version explicitly. Predict both lines.
Solution
#include <iostream>
using namespace std;
 
class Shape {
public:
    void describe() const { cout << "shape\n"; }
};
 
class Square : public Shape {
public:
    void describe() const { cout << "square\n"; }
};
 
int main() {
    Square s;
    s.describe();
    s.Shape::describe();
    return 0;
}

The output is square and then shape.

  1. Write a Base class with a protected int value and a Derived class that doubles it in a method doubleIt(). Starting from 5, call doubleIt twice and print the value after each call. Expected: 10 then 20.
Solution
#include <iostream>
using namespace std;
 
class Base {
protected:
    int value = 5;
};
 
class Derived : public Base {
public:
    void doubleIt() { value *= 2; }
    int get() const { return value; }
};
 
int main() {
    Derived d;
    d.doubleIt();
    cout << d.get() << "\n";
    d.doubleIt();
    cout << d.get() << "\n";
    return 0;
}

The derived class changes the protected member directly, so the output is 10 and then 20.

  1. The following code does not compile. Find the fix that keeps the inheritance public.
class Animal {
public:
    void eat() {}
};
class Dog : Animal {
};
int main() {
    Dog d;
    Animal* a = &d;
}
Solution

Write class Dog : public Animal. The default inheritance for class is private, which hides Animal from code outside Dog, so the pointer conversion fails.

  1. Write a Logger base class with a method log(string) that prints [log] followed by the message and a newline. Derive FileLogger whose log first prints [file] (no newline) and then calls the base version. Call log("start") on a FileLogger. Predict the single output line.
Solution
#include <iostream>
#include <string>
using namespace std;
 
class Logger {
public:
    void log(const string& msg) const {
        cout << "[log] " << msg << "\n";
    }
};
 
class FileLogger : public Logger {
public:
    void log(const string& msg) const {
        cout << "[file] ";
        Logger::log(msg);
    }
};
 
int main() {
    FileLogger f;
    f.log("start");
    return 0;
}

The derived method prints its prefix, then the base method prints [log] start. Both parts appear on one line, because [file] has no newline.

  1. Explain slicing in your own words and give one way to avoid it.
Solution

Slicing is copying a derived object into a base-class object by value, which keeps only the base part and loses the derived data and behaviour. Avoid it by passing the object by const reference or by pointer.

  1. Write two classes, Engine with a method start() that prints engine on, and Car that has an Engine member (composition, not inheritance) and a method start() that calls the engine's start(). Explain in one sentence why this is composition and not inheritance.
Solution
#include <iostream>
using namespace std;
 
class Engine {
public:
    void start() const { cout << "engine on\n"; }
};
 
class Car {
    Engine engine;
public:
    void start() const { engine.start(); }
};
 
int main() {
    Car c;
    c.start();
    return 0;
}

The output is engine on. This is composition because a car has an engine. A car is not a kind of engine, so inheritance would be the wrong relationship.

What you now know

  • Inheritance models an is-a relationship. The derived class gets the base's members and can add its own.
  • The base part sits inside the derived object. It is built first and destroyed last.
  • The derived constructor passes arguments to the base in its initialiser list.
  • Access levels: public is visible everywhere, protected is visible to derived classes, and private is visible only in the class itself.
  • A derived method with the same name hides the base version. Base::method() calls the base version explicitly.
  • Slicing silently cuts the derived part off when you copy by value. Pass by reference or pointer.
  • Common errors are a missing base argument, a private inheritance that hides the base, and a final base.
  • Next, lesson 4 uses virtual so that a base pointer calls the right derived method.
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.

← Constructors, destructors and this