C++ inheritance interview questions, with answers
C++ inheritance has more moving parts than inheritance in Java or Python, because C++ lets you choose what is virtual, how a base class is constructed, and how a pointer to a base is converted back. The OOP pages already answer the questions about virtual dispatch, vtables, slicing, the diamond and virtual destructors; this page takes the C++-specific ones they leave out, each with code that was compiled and run.
The questions cover constructing a base class, names that a derived class hides, the casts that move between base and derived, run-time type information, and copying an object you only know through a base pointer. Then take the free C++ diagnostic — ten questions across every C++ topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.How do you pass arguments to a base-class constructor in C++?
In short: Name the base class in the derived constructor's member initialiser list, as in Derived(int x) : Base(x) {}; the base is always constructed before the derived class's own members.
A derived object contains its base subobject, and that base must be fully constructed before the derived constructor's body runs, so C++ does not let you call the base constructor from the body. Instead the derived constructor lists it first in its initialiser list, Base(x), followed by its own members; if you omit it, the base's default constructor is used, and if the base has none, the derived constructor does not compile. The order is fixed regardless of how you write the list: base classes in the order they are declared, then members in declaration order, then the constructor body. Since C++11, using Base::Base; inherits the base's constructors wholesale when the derived class adds no data of its own.
struct Shape { std::string name; Shape(std::string n) : name(n) {} }; struct Square : Shape { int side; Square(int s) : Shape("square"), side(s) {} }; int main() { Square q(4); std::cout << q.name << " " << q.side; } // square 4
2.What is name hiding in C++ inheritance, and how does using fix it?
In short: Declaring a function in a derived class hides every base-class function with that name, whatever its parameters; a using-declaration brings the base overloads back into scope.
Name lookup in C++ happens before overload resolution and stops at the first scope that contains the name. If Derived declares any member called print, lookup finds it in Derived and never looks in Base, so Base's print(int) is hidden even though its parameters differ, and d.print(7) either picks Derived's version through a conversion or fails. That surprises people who expect the overloads to merge, as they would within one class. Writing using Base::print; inside Derived adds the base overloads to Derived's scope, after which normal overload resolution chooses between all of them, as below. The same rule applies to virtual functions, where an override of one overload hides the others, and g++ warns about it with -Woverloaded-virtual.
struct Logger { void print(int) { std::cout << "int\n"; } }; struct FileLog : Logger { using Logger::print; void print(std::string) { std::cout << "text\n"; } }; int main() { FileLog f; f.print(7); } // int
3.What is the difference between static_cast and dynamic_cast when downcasting in C++?
In short: static_cast trusts you and converts at compile time with no check; dynamic_cast checks the object's real type at run time and gives nullptr for a pointer that is not of the target type.
Converting a Base* to a Derived* is a downcast, and it is only valid if the object really is a Derived. static_cast performs it without any check, so it costs nothing, but if the object is some other type the result is undefined behaviour. dynamic_cast asks the object's run-time type information: it needs a polymorphic base, one with at least one virtual function, and returns the converted pointer when the object is a Derived, or a derived class of it, and nullptr otherwise, as below. That makes it safe but slower, and needing it often suggests the behaviour belongs in a virtual function instead. dynamic_cast can also cast sideways across multiple inheritance, which static_cast cannot.
struct Pet { virtual ~Pet() = default; }; struct Fish : Pet {}; struct Bird : Pet {}; int main() { Pet* p = new Bird; auto* b = dynamic_cast< Bird*>(p); auto* f = dynamic_cast< Fish*>(p); std::cout << (b != nullptr) << ' ' << (f != nullptr); delete p; } // 1 0
4.What is RTTI in C++, and what does typeid tell you?
In short: Run-time type information lets a program ask an object's dynamic type: typeid on a polymorphic object gives its real class, and dynamic_cast is built on the same data.
For a class with at least one virtual function, every object carries a link to type information about its dynamic class. typeid(*p) consults it, so comparing typeid(*p) == typeid(Circle) tells you what the object really is even when p is a Shape*, as below. On a class with no virtual functions, typeid reports the static type instead, and applying typeid to a dereferenced null pointer of polymorphic type throws std::bad_typeid. The name() string is implementation-defined, and g++ returns a mangled name such as 6Circle, so it is fit for logging, not for logic. RTTI can be disabled with -fno-rtti in code bases that avoid its cost, which also disables dynamic_cast.
struct Shape { virtual ~Shape() = default; }; struct Circle : Shape {}; int main() { Shape* s = new Circle; bool same = typeid(*s) == typeid(Circle); std::cout << same; delete s; } // 1
5.Why can't a C++ constructor be virtual, and how do you copy an object through a base pointer?
In short: A constructor creates the object, so there is no dynamic type yet to dispatch on; the virtual clone idiom, a virtual function that returns a copy of *this, fills the gap.
Virtual dispatch uses the object's dynamic type, but a constructor runs before the object exists, and the caller must already name the exact type to construct, so a virtual constructor would be meaningless. The practical need behind the question is copying an object when all you hold is a base pointer, and Base b = *p would slice it. The virtual clone idiom solves it: the base declares a virtual function that returns a new copy, and each derived class overrides it to copy itself. Covariant return types let the override return Pen* while the base returns Tool*, and wrapping the result in std::unique_ptr straight away, as below, makes ownership of the new object explicit.
struct Tool { virtual ~Tool() = default; virtual Tool* clone() const = 0; virtual int id() const = 0; }; struct Pen : Tool { Pen* clone() const override { return new Pen(*this); } int id() const override { return 7; } }; int main() { std::unique_ptr<Tool> a( new Pen); std::unique_ptr<Tool> b( a->clone()); std::cout << b->id(); } // 7
6.How do you write an interface in C++?
In short: Write an abstract class with only pure virtual functions and a virtual destructor, and no data; a class implements the interface by inheriting from it publicly and overriding every function.
C++ has no interface keyword. The idiom is a class whose member functions are all pure virtual, declared with = 0, plus a public virtual destructor, so deleting through the interface is safe, and no data members, so implementing several interfaces with multiple inheritance never duplicates state. A concrete class inherits publicly and overrides each function, marking them override, and code that depends only on the interface can take a reference or pointer to it, as the print function below does. Because the interface has no state, the diamond problem that makes multiple inheritance of classes awkward does not arise. C++20 concepts offer a different, compile-time form of interface for templates, with no virtual calls at all.
struct IText { virtual ~IText() = default; virtual std::string text() const = 0; }; struct Invoice : IText { std::string text() const override { return "INV-7"; } }; void print(const IText& p) { std::cout << p.text(); } int main() { print(Invoice{}); } // INV-7
How the diagnostic asks it
One question from the C++ bank, exactly as a sitting would show it. The bank has 3 on inheritance & virtual functions and 30 across C++.
What happens when this C++ code is compiled?
struct Base { virtual void f() {} }; struct Derived : Base { void g() {} }; int main() { Base* p = new Derived; p->g(); }
- 1It does not compile: Base has no member named gcorrect
- 2It calls Derived::g, because p points to a Derived
- 3It compiles, but the call fails at run time
- 4It calls Base::f instead
Which member names a call may use is checked at compile time against the pointer's static type, Base*, and Base has no member g, so the call is rejected: 'struct Base' has no member named 'g'. Virtual dispatch only chooses between overrides of a function that Base declares; it never adds names. That is why calling Derived::g is wrong even though the object is a Derived, and why nothing can fail at run time: the code never compiles. Calling Base::f instead is not how any call is resolved. To reach g, either declare it as a virtual function in Base, or convert the pointer to Derived* first.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 C++ questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.