xSKILLby Infirals
Register

C++ for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Base and derived classes

Inheritance, operators, and templates12 min readLesson 1 of 5

1.1 An is-a relationship

Public inheritance models an is-a relationship. A Dog is an Animal, so code that works with an Animal should also accept a Dog. The derived class inherits the base class's members, and it can add members of its own.

The syntax class Dog : public Animal places Animal as a public base. The public keyword keeps the base's public members public in the derived class and its protected members protected. Private members of the base stay inaccessible to the derived class, although they still exist in every derived object.

cpp
#include <iostream>
#include <string>
class Animal {
public:
    explicit Animal(std::string name) : name_(name) {}
    void eat() const { std::cout << name_ << " eats\n"; }
protected:
    std::string name_;
};
class Dog : public Animal {
public:
    Dog() : Animal("Dog") {}
    void fetch() const { std::cout << name_ << " fetches\n"; }
};
int main() {
    Dog dog;
    dog.eat(); dog.fetch();
    return 0;
}

Output

Dog eats
Dog fetches

1.1.1 Construction runs from the base down

When a derived object is created, the base part is constructed first, then the derived class's members, then the derived constructor body. Destruction reverses that order. A derived constructor passes arguments to the base constructor in its initializer list.

If the base has no default constructor, every derived constructor must name one. Dog() : Animal("Dog") {} calls the base constructor with a string. Without that call, the compiler tries to use a base default constructor and reports an error when none exists.

cpp
#include <iostream>

class Vehicle {
public:
    explicit Vehicle(int wheels) : wheels_(wheels) {
        std::cout << "vehicle with " << wheels_ << " wheels\n";
    }
protected:
    int wheels_;
};
class Bike : public Vehicle {
public:
    Bike() : Vehicle(2) { std::cout << "bike ready\n"; }
};
int main() {
    Bike bike;
    return 0;
}

Output

vehicle with 2 wheels
bike ready

1.1.2 Protected members

A protected member is visible to the class and its derived classes, and hidden from the rest of the program. It lets a derived class reuse base state without exposing that state as part of the public interface.

Protected data is still shared state. A derived class can change it and break a base class rule, and every derived class becomes coupled to the base representation. Prefer private data with protected member functions when derived classes need controlled access. Use protected data for small hierarchies where the base is designed for extension.

cpp
#include <iostream>

class Account {
protected:
    void set_balance(int balance) { balance_ = balance; }
public:
    int balance() const { return balance_; }
private:
    int balance_ = 0;
};
class Savings : public Account {
public:
    void deposit(int amount) { set_balance(balance() + amount); }
};
int main() {
    Savings account;
    account.deposit(40);
    std::cout << "Balance: " << account.balance() << "\n";
    return 0;
}

Output

Balance: 40

1.1.3 Name hiding

A member function in a derived class hides base functions with the same name, even when the parameter lists differ. If Logger has log(int) and TimedLogger declares log(const std::string&), then timed.log(7) does not compile, because the derived name hides the whole base overload set.

Call the hidden function with a qualified name: timed.Logger::log(7). A using Logger::log; declaration inside the derived class brings the base overloads back into the derived scope. Hiding is easy to miss because the base function still exists; the compiler simply stops looking for it once it finds the derived name.

cpp
#include <iostream>
#include <string>

class Logger {
public:
    void log(int code) const { std::cout << "code " << code << "\n"; }
};
class TimedLogger : public Logger {
public:
    void log(const std::string& message) const {
        std::cout << "[time] " << message << "\n";
    }
};
int main() {
    TimedLogger logger;
    logger.log("ready");
    logger.Logger::log(7);
    return 0;
}

Output

[time] ready
code 7

1.1.4 Composition and inheritance

Inheritance answers an is-a question. Composition answers a has-a question: a Car has an Engine, so an Engine member makes more sense than an Engine base. Composition reuses a class without claiming that the outer type is a kind of the inner type.

Composition also keeps interfaces independent. An Engine can change its functions, and Car decides which of them to expose. Inheritance ties the derived class to the base interface, and a change to the base reaches every derived class. When both designs seem possible, composition is usually easier to change later.

cpp
#include <iostream>

class Engine {
public:
    void start() const { std::cout << "engine started\n"; }
};
class Car {
public:
    void start() const {
        engine_.start();
        std::cout << "car ready\n";
    }
private:
    Engine engine_;
};
int main() {
    Car car;
    car.start();
    return 0;
}

Output

engine started
car ready

1.1.5 Common mistakes

  • Forgetting public in the base list, which makes a class use private inheritance.
  • Expecting a derived class to reach private base members.
  • Forgetting to call a base constructor that has no default constructor.
  • Assuming a derived name overloads a base function when it hides it.
  • Using inheritance for code reuse when the types have no is-a relationship.
  • Destroying a derived object through a base pointer that has no virtual destructor.

1.1.6 Practice

  1. Write an Animal base with a name and an eat function, then a Cat class that adds purr.
  2. Give the base a constructor that takes a string and call it from the derived constructor.
  3. Move a base member from protected data to a protected function and update the derived class.
  4. Replace an inheritance relationship with a member object and compare the two call sites.
  5. Explain why timed.log(7) fails when the derived class declares log(const std::string&).

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01In what order are base and derived parts constructed?

    The base class part is constructed first, then data members in declaration order, then the derived constructor body. Destruction runs in the opposite order.

  • 02What does protected access allow?

    Derived classes can use the member, and code outside the hierarchy cannot. It keeps the member out of the public interface while giving derived classes direct access.

  • 03Why might composition be a better choice than inheritance?

    Composition reuses another class as a member and does not claim an is-a relationship. It keeps the two types independent, so changes to the member's interface do not silently change inherited behavior.

Quiz

Check: C++ inheritance

Four questions on is-a relationships, construction order, protected access and name hiding.

4 questions

Question 1 of 4

0/4 answered

When a derived object is created, which part is constructed first?

Answer all you can, then submit.