This post will be short, however I think it could be useful for people, especially the ones coming from Java to C++. Today I want to cover inheritance, which may seem like a trivial thing, but actually it's not. Maybe not at its core - every Java developer will be able to understand it in C++. I would like to concentrate more on the semantics of it.
Scope first
Let's start with scopes in C++, as this is an easy one. We have only three scopes in C++, which are known to us already (coming from Java) - public, protected and private. No surprises there at all. Public means public (visible to everyone), protected is protected - only children of specific class have access to such members. At the end there's private modifier - members accessible only from within the class. So far everything clear.
The problem arises when it comes to writing things down in the code. In Java, we're used not only, to have access modifiers in the front of the class, but also to specify them for every method (I omit default public modifier in interfaces, let's not get distracted). In C++, we actually group class members in the scope sections which look like this:
class MyClass
{
public:
int someMethod();
protected:
int someOtherMethod();
private:
int x, y;
}
Their order does not matter, and what is more - there can be a couple of them! Below class will work just fine:
class MyClass
{
public:
int someMethod();
protected:
int someOtherMethod();
private:
int x, y;
public:
int someMethod2();
private:
int z;
}
Looking weird, right? To be honest - I did not reach 'C++ style guide' yet, but I expect that there's a tip to use only single scope sections in a class. Ok, let's move on.
Inheritance
Scope inheritance
When we're inheriting from one class, we have to use : operator to indicate that. At the same place, we can specify one of the access modifiers, that will decide, what are the child class members' visibility. The base construction looks like this:
class Parent {};
class Child : ACCESS-MODIFIER Parent {};
In the place of ACCESS-MODIFIER we can put one of the already known modifiers - public, protected or private. When the modifier is not specified, it defaults to private! Based on the used modifier, inherited class members have the following access modifiers:
- Public
- public remain public
- protected remain protected
- private remain private
- Protected
- public becomes protected
- protected remain protected
- private remain private
- Private
- public become private
- protected become private
- private remain private
Methods inheritance
As C++ is OO-language, obviously there must be also inheritance of methods' behaviour, right?. However, it also works differently than in Java. Let's see an example:
class Parent {
public void someMethod() {
System.out.println("Parent");
}
}
class Child extends Parent {
public void someMethod() {
System.out.println("Child");
}
}
public static void main(String []args) {
Parent p = new Parent();
Child c = new Child();
Parent referenceToChildUsingParent = c;
p.someMethod();
c.someMethod();
referenceToChildUsingParent.someMethod();
}
Code is as simple as possible. If there's a method in the parent class, and we don't limit its scope in the child, there's no problem with inheriting it, and overriding. Output would be:
Parent
Child
Child
With C++ it's not the same. An equivalent of the above Java code should look like this:
#include<iostream>
class Parent {
public:
void say() {
std::cout << "Parent says hi!" << "\n";
}
};
class Child : public Parent {
public:
void say() {
std::cout << "Child says hi!" << "\n";
}
};
int main()
{
Parent parent;
Child child;
Parent& referenceToChildUsingParent = child;
parent.say();
child.say();
referenceToChildUsingParent.say();
}
Can you guess the output? Here it is:
Parent says hi!
Child says hi!
Parent says hi! // Yes, that's not a mistake!
By default, when no other modifiers are specified, type of the reference 'wins'. In order to achieve the same result as in Java, we have to tell the compiler that we want to use a derived class method implementation when it is present. Here's the code:
#include<iostream>
class Parent {
public:
virtual void say() { // Notice the usage of VIRTUAL here
std::cout << "Parent says hi!" << "\n";
}
};
class Child : public Parent {
public:
void say() {
std::cout << "Child says hi!" << "\n";
}
};
int main()
{
Parent parent;
Child child;
Parent& referenceToChildUsingParent = child;
parent.say();
child.say();
referenceToChildUsingParent.say();
}
The output now is the same. Virtual acts similar to default modifier in Java - there's a present implementation of method in the parent, and it's used by default. However, if a method is overridden in the child class, it is being used. Usage of virtual can be strengthened with usage of override keyword in the children classes. They act like @override annotation in Java - indicating the fact, that the method is inherited. When we use override, and there's no matching method signature in the parent class, a compiler will fail.
class Parent {};
class Child : public Parent {
public:
void say() override { // This line will cause compilation error
std::cout << "Child says hi!" << "\n";
}
};
In Java, in addition to default keyword (introduced in Java 8, and applying only to interfaces), we have good old abstract modifier. As a reminder - classes/methods declared as abstract, cannot be instantiated. The same applies in C++, although the syntax is different. What is more, we call these types of methods (requiring concrete implementations in children) - pure virtual methods/functions. Let's take a look:
#include<iostream>
class Parent {
public:
virtual void say() =0; // =0 is an equivalent of 'abstract'
};
class Child : public Parent {
public:
void say() override {
std::cout << "Child says hi!" << "\n";
}
};
int main()
{
// Parent parent; This line will cause compilation error when uncommented
Child child;
Parent& referenceToChildUsingParent = child;
child.say();
referenceToChildUsingParent.say();
}
Using this syntax, it's possible to actually achieve interface functionality in C++, although there's no such concept in it (supported by the language as in Java). Just declare all the class methods as pure ones, and it's a state-of-art interface at hand. Here one important thing should be mentioned - it's a good practice, which can prevent resources from leaking - always implement virtual destructor in the base class/interface!. It's enough to use
keyword for that - indicating that we let compiler generate the body of this method automatically.
```cpp
class Parent {
public:
virtual void say() =0;
virtual ~Parent() =default;
};
Ok, where's my class access modifier?
Well, actually, there's none. That's right. There's no concept of the class access modifier. Everything is decided on the class
members level, and there's nothing that we can do about it. Coming from Java world it seems like a huge limitation. However, concept of C++ modules, which was introduced in C++20, seems to help here, but I don't want to dive into it yet. I have plans to write a separate post about it in the future.
Friend functions
'Speak friend, and enter' comes right to mind, right? Again - I was surprised to discover this concept in C++. It's something-almost-like package-private modifier in Java, although that's not exactly it. In short - friend functions are able to actually access private and protected elements of the class, while not being their members. Wat? Yup, you read it right. Below code should shed some light on the concept, although this functionality is way more complicated than below example (taken from official CPP reference).
class Y {
int data; // private member
// the non-member function operator<< will have access to Y's private members
friend std::ostream& operator<<(std::ostream& out, const Y& o);
friend char* X::foo(int); // members of other classes can be friends too
friend X::X(char), X::~X(); // constructors and destructors can be friends
};
// friend declaration does not declare a member function
// this operator<< still needs to be defined, as a non-member
std::ostream& operator<<(std::ostream& out, const Y& y)
{
return out << y.data; // can access private member Y::data
}
SOURCES:
- Josh Lospinoso 'C++ Crash Course' book
- CPP reference about friend functions and objects
- SO question when you should use friend functions
Top comments (0)