You get a bonus - 1 coin for daily activity. Now you have 1 coin

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Lecture



Inheritance and polymorphism are two key concepts of object-oriented programming (OOP).

Inheritance allows you to create new classes that inherit properties and methods from existing classes (parent classes), thereby extending their functionality. A child class can use the properties and methods of the parent class, as well as add its own properties and methods.

Polymorphism allows the same method to be used with different classes derived from the same base class, without worrying about the type of the object. This means that different classes can have methods with the same name but different implementations.

An example of inheritance and polymorphism in OOP:

We have a base class "Shape", which contains methods for calculating area and perimeter. The classes "Circle", "Square" and "Rectangle" inherit from it. Each of these classes implements its own method for calculating area and perimeter. For example, the area-calculation method for a circle takes a radius, for a square - the side length, and for a rectangle - the length and width.

When we call the area-calculation method for each of these shapes, we can use the same method without worrying about what type of shape we are passing as the parameter. This is called polymorphism.

Inheritance in OOP

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Inheritance - is one of the four main mechanisms of object-oriented programming (alongside encapsulation, polymorphism and abstraction) that makes it possible to describe a new class based on an already existing (parent) class, whose properties and functionality are then inherited by, and can be overridden in, the new class.

A descendant class implements the specification of an already existing class (the base class). This makes it possible to work with objects of the descendant class in exactly the same way as with objects of the base class.

Simple inheritance:
A class from which inheritance is performed is called the base or parent class (in English: base class). Classes that are derived from the base class are called descendants, heirs, or derived classes (in English: derived class).

Some languages use abstract classes. An abstract class is a class that contains at least one abstract method; it is described in the program, has fields and methods, and cannot be used to directly create an object. From an abstract class one can only inherit. Objects are created only on the basis of derived classes that inherit from the abstract one. For example, an abstract class could be the base class «university employee», from which the classes «graduate student», «professor», etc. descend. Since the derived classes have common fields and functions (for example, the field «year of birth»), these class members can be described in the base class. In the program, objects are created based on the classes «graduate student», «professor», but no objects can be created based on the class «university employee».

Multiple Inheritance
With multiple inheritance, a class can have more than one ancestor. In this case, the class inherits the methods of all its ancestors. The advantage of this approach is greater flexibility. Multiple inheritance is implemented in C++. Some other languages also support it, such as Python and Eiffel . Multiple inheritance is supported in UML.

Multiple inheritance is a potential source of errors that can arise from having identically named methods among the ancestors. In languages that position themselves as successors to C++ (Java, C#, etc.), it was decided to abandon multiple inheritance in favor of using interfaces. In practice, it is almost always possible to do without this mechanism. However, if it is needed, conflicts can still be resolved by, for example, using the scope-resolution operator «::» to call a specific method of a specific parent.

An attempt to solve the problem of identically named methods existing among predecessors was made ​​in the Eiffel language, in which the description of a new class must explicitly specify the imported members of each of the inherited classes and their names in the child class.

Most modern object-oriented programming languages (C#, Java, Delphi, etc.) support the ability to simultaneously inherit from an ancestor class and implement the methods of multiple interfaces from one or more classes. In many respects, this mechanism replaces multiple inheritance - interface methods must be explicitly overridden, which rules out errors when inheriting functionally identical methods from different ancestor classes.

Private inheritance - is when the access specifier is set to private, so that all public and protected members of the base class become private members of the derived class. Private inheritance means implementation by means of - that is, some functionality needs to be taken from the base class, and the base class and its descendant have no conceptual relationship. Private inheritance does not have the character of a subtype relationship. Private (as well as protected) inheritance does not create a type hierarchy.

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

There are several scenarios in which it is worth using private inheritance instead of aggregation:

  • In the case of private inheritance, it is possible to override abstract and virtual methods.
  • Private inheritance allows an instance of the descendant to be used polymorphically within a limited scope (for example, in a friend function). But in the general sense, there is no IS-A relationship between the descendant and the base class.
  • With private inheritance, the descendant can access the protected members of the base class.
  • Private inheritance makes it possible to implement "narrowing" inheritance, where the descendant exposes only a few methods of the base class (by raising their visibility with a using-declaration: using Base::ProtectedMethod).

If all of this is needed, then private inheritance will be more preferable than aggregation.

Private Inheritance and Its Alternative - Composition.

Aggregation or composition – is when an object of another class becomes a member of one class:

Example of using composition:

Let class D have a member of class B.

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

In turn, class B has a member of class C.

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Thus, by using composition we build a hierarchy of objects. From a design standpoint, private inheritance is equivalent to composition, except for the issue of overriding functions

In general, there is a fundamental difference between composition and (non-private) inheritance:

Composition — is a has-a relationship (has). Using composition, we build a hierarchy of objects.

Inheritance — is an is-a relationship (is). Using inheritance (non-private), we build a hierarchy of classes.

Example: a car has a steering wheel, wheels, etc.

And a car is a vehicle.

There are several scenarios in which it is worth using private inheritance instead of composition:

  1. It is necessary to override abstract and/or virtual methods.

  2. It is necessary to use an instance of the descendant polymorphically within a limited scope (for example, in a friend function). But in the general sense, there is no IS-A relationship between the descendant and the base class.

  3. With private inheritance, the descendant can access the protected members of the base class.

  4. Private inheritance makes it possible to implement "narrowing" inheritance, where the descendant exposes only a few methods of the base class (by raising their visibility with a using-declaration: using Base::ProtectedMethod).

If all of this is needed, then private inheritance will be more preferable than composition.

Polymorphism in OOP

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes
Polymorphism - the ability of objects with the same specification to have different implementations.

A programming language supports polymorphism if classes with the same specification can have different implementations - for example, a class's implementation can be changed at run time.

The essence of polymorphism can be expressed in the phrase: «One interface, many implementations».

Polymorphism is one of the four important mechanisms of object-oriented software (along with abstraction, encapsulation and inheritance).

Polymorphism makes it possible to write more abstract programs and increase the code reuse ratio. The common properties of objects are combined into a system that can be called by different names - interface, class.

The definition has both an external and an internal expression:

external commonality manifests as an identical set of methods with the same names and signatures (i.e., the names and the number and types of the arguments);
internal commonality - identical functionality of the methods. This can be described intuitively, or expressed as strict laws, rules that the methods must necessarily satisfy. When one method (function, operation) is defined multiple times with different behavior, this is called method overloading (function overloading, operator overloading).

There are fundamentally different types of polymorphism:

  • - ad hoc - the function describes different implementations (possibly with different behavior) for a limited set of explicitly specified types and their combinations.
  • - parametric - the code is written independently of a specific data type and can therefore be freely used with any new data types;
  • - subtype polymorphism – allows a function operating on a type Z to also execute correctly on arguments of type J, which is a subtype of Z.


Forms of polymorphism


Using parametric polymorphism, universal base types can be created. In the case of parametric polymorphism, a function is implemented once and behaves the same for all types, just as it would for a single specific type. Parametric polymorphism deals with parametric methods and types.

Parametric types.

Instead of writing a class for each specific type, you should create types that will be resolved at program run time, that is, we create a parametric type.

Override Polymorphism.

Abstract methods often belong to the category of deferred methods. The class in which this method is defined can call the method, and polymorphism ensures that the appropriate version of the deferred method is called in the child classes. Ad hoc polymorphism allows a special implementation for the data of each type.

Overload Polymorphism

Overload polymorphism is a special case of polymorphism.

Example in Python:

class Shape:
def area(self):
pass

def perimeter(self):
pass


class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self):
return 3.14 * self.radius * self.radius

def perimeter(self):
return 2 * 3.14 * self.radius


class Square(Shape):
def __init__(self, side):
self.side = side

def area(self):
return self.side * self.side

def perimeter(self):
return 4 * self.side


class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width

def area(self):
return self.length * self.width

def perimeter(self):
return 2 * (self.length + self.width)


shapes = [Circle(5), Square(10), Rectangle(3, 4)]

for shape in shapes:
print("Area: ", shape.area())
print("Perimeter: ", shape.perimeter())

Thus

  • Inheritance in OOP makes it possible to create a new class based on an existing one, which inherits its properties and methods, while being able to extend or modify them according to specific needs.
  • Polymorphism in OOP means using the same name to denote different objects, which makes it possible to simplify the code and make it more universal. Different objects can have the same interface but provide different implementations of the methods.

Both of these principles are important OOP concepts and make it possible to create more flexible and scalable software systems, making code reuse easier and reducing the amount of code written.

Inner Class

An inner, or nested class (English: inner class) — in object-oriented programming, a class that is defined entirely within another class.

Nested classes are supported in the Java programming language starting from version 1.1, in C# and other languages on the .NET platform, as well as in the D programming language and in C++.

An instance of an ordinary (outer) class can exist as an independent object. Its existence does not require the mandatory presence of definitions of other classes or their instances. In the case of an inner class, its instance cannot exist without being bound to the top-level class that encloses it, or to an instance of that class.

Inner Classes in the Java Language

In Java there are 4 types of inner classes:

Inner (Non-Static) Classes

An instance of an inner class can exist only when a specific instance of the outer class exists. This logical relationship determines the syntax for creating objects: first an object of the outer class is created, and later, based on it, an object of the inner class is created.

Inner non-static classes are described inside the main outer class. Instances of such classes have access to the public, protected, default and private fields of the outer class, as well as to the static and non-static methods of the outer instance with any access modifiers. Because instances of an inner class are always logically bound to instances of the enclosing class, they cannot contain (although they can inherit from an ancestor) definitions of static fields, methods and classes (except constants).

Example of declaring an inner class:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Creating the described class can be expressed with the following block of code: OuterClass.InnerClass inner = new OuterClass().new InnerClass();

Static Nested Classes

They are declared inside the main class and are marked with the static keyword. Objects of such classes do not have access to the members of the outer class, except for the static ones. This is because a specific object of the outer class is not used to create such a class, and at the moment the inner class's code is executed, an object of the outer class may not exist at all. Instances of static nested classes can contain static fields, methods and classes, unlike other types of inner classes.

Example of declaring a nested static class:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Creating the described static nested class can be expressed with the following block of code: OuterClass.StaticInnerClass staticInner = new OuterClass.StaticInnerClass();

Local Classes

They are declared inside the methods of the main class. They can be used only within those methods. They have access to the members of the outer class. They have access to both local variables and method parameters under one condition - the variables and parameters used by the local class must be declared final. They cannot contain the definition of (but can inherit) static fields, methods and classes (except constants).

Example:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

The described local class can only be created inside the method itself, strictly below the code that declares the class. Example creation code: InnerLocalClass innerLocal = new InnerLocalClass();

Anonymous (Unnamed) Classes

They are declared inside the methods of the main class. They can be used only within those methods. Unlike local classes, anonymous classes do not have a name. The main requirement for an anonymous class - it must inherit an existing class or implement an existing interface. They cannot contain the definition of (but can inherit) static fields, methods and classes (except constants). Example:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

What is the difference between nested and inner classes?
Nested classes declared statically are called static nested classes. Inner classes — are when an object of the inner class is bound to an object of the enclosing class. Non-static nested classes are called inner classes if they are bound to the outer class.

Inner Classes in Other Programming Languages

PHP 7 has a mechanism for describing anonymous classes; however, unlike Java, anonymous classes are not required to inherit an existing class or implement an existing interface, which is made possible by the dynamic nature of the language. Example:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Friend Classes, Friend Functions, Program Examples.

Thus we have examined the merits of keeping data hidden, but sometimes you may encounter situations where you find that you have classes and functions outside those classes that need to work very closely with each other. For example, you might have a class that stores data, and a function (or another class) that displays this data on the screen. Although the storage class and the display code were separated to simplify maintenance, the display code is actually closely tied to the details of the storage class. Consequently, hiding information about the storage classes from the display code provides little real benefit.

In such cases there are two options:

  1. Use the public functions of the storage class in the display code. However, this has several potential drawbacks. First, these public member functions must be defined, which takes time and can clutter the interface of the storage class. Second, the storage class may have to provide functions for the display code that it does not want to make available to anyone else. But there is no way to say «this function is intended for use only by the display class».
  2. Alternatively, by using friend classes and friend functions, you can give your display code access to the private details of the storage class. This allows the display code to directly access all the private members and functions of the storage class, without giving access to anyone else! In this lesson we will take a closer look at how this is done.

Friend Functions

A friend function – is a function that can access the private members of a class as if it were a member of that class. In all other respects, a friend function is like an ordinary function. A friend function can be either an ordinary function or a member function of another class. To declare a friend function, simply use the keyword friend before the prototype of the function you want to make a friend of the class. It does not matter whether you declare the friend function in the private or the public section of the class.

Here is an example of using a friend function:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

In this example, we made a function called reset(), which takes an object of class Accumulator and sets the value of m_value to 0. Since reset() is not a member of class Accumulator, normally reset() would not have access to the private members of Accumulator. However, since Accumulator specifically declared this function reset() as a friend of the class, it is granted access to the private members of Accumulator.

Note that we must pass into reset() an Accumulator object. This is because reset() is not a member function. It has no *this pointer and no Accumulator object to work with unless one is specified.

Here is another example:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

In this example, we declare the function isEqual() a friend of class Value. isEqual() takes two Value objects as parameters. Since isEqual() is a friend of class Value, it can access the private members of any Value object. In this case, it uses this access to compare the two objects and returns true if they are equal.

Although both of the examples above are rather contrived, the latter example is very similar to cases we will encounter later when we discuss operator overloading!

Multiple Friends

A function can be a friend of more than one class at the same time. For example, consider the following example:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Two things are worth noting in this example. First, since printWeather is a friend of both classes, it can access the private data of objects of both classes. Second, note the following line at the top of the example:

class Humidity;

This is a class prototype (forward declaration), which tells the compiler that we are going to define a class named Humidity later. Without this line, the compiler, when parsing the prototype for printWeather() inside class Temperature, would tell us that it does not know what Humidity is. Class prototypes serve the same role as function prototypes – they tell the compiler what something looks like so that it can be used now and defined later. However, unlike functions, classes have no return types or parameters, so class prototypes are always simply class ClassName, where ClassName – is the name of the class.

Friend Classes

An entire class can also be made a friend of another class. This gives all the members of the friend class access to the private members of the other class. Here is an example:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Since class Display is a friend of Storage, any of the members of Display that use a Storage class object can directly access the private members of Storage. This program produces the following result:

6.7 5

A few additional notes about friend classes.

First, even though Display is a friend of Storage, Display does not have direct access to the *this pointer of Storage objects.

Second, the fact that Display is a friend of Storage does not mean that Storage is also a friend of Display. If you want two classes to be friends with each other, they must both declare each other as friends. Finally, if class A is a friend of B, and B – is a friend of C, this does not mean that A is a friend of C.

Be careful when using friend functions and classes, because this allows the friend function or class to break encapsulation. If the implementation details of a class change, the implementation details of the friend will also need to be changed. Therefore, limit the use of friend functions and classes to a minimum.

Friend Member Functions

Instead of making the entire class a friend, you can make just a single member function a friend. This is done the same way as making an ordinary function a friend, except that you use the member function's name with the ClassName:: prefix included (for example, Display::displayItem).

However, this can actually be a bit trickier than expected. Let's convert the previous example to make Display::displayItem a friend member function. You might try something like this:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

However, it turns out that this will not work. To make a member function a friend, the compiler must see the full definition of the class the member function belongs to (not just a forward declaration). Since class Storage has not yet seen the full definition of class Display, the compiler will produce an error at the point where we try to make the member function a friend.

Fortunately, this is easy to fix, simply by putting the definition of class Display before the definition of class Storage.

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

However, now we have a different problem. Since the member function Display::displayItem() uses Storage as a reference parameter, and we just moved the definition of Storage below the definition of Display, the compiler will complain that it does not know what Storage is. We cannot fix this by changing the order of the definitions, because then we would undo the previous fix.

Fortunately, this too can be fixed by performing a couple of simple steps.

First, we can add class Storage as a forward declaration.

Second, we can move the definition of Display::displayItem() out of the class to a place after the full definition of class Storage.

Here is what this looks like:

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Inheritance and Polymorphism in OOP: Inner and Nested Classes, Friend Functions and Classes

Now everything will compile correctly: the forward declaration of class Storage is enough to satisfy the declaration of Display::displayItem(), the full definition of Display satisfies the declaration of Display::displayItem() as a friend of Storage, and the full definition of class Storage is enough to satisfy the definition of the member function Display::displayItem(). If this is a bit confusing, see the comments in the program.

If this seems complicated – it is. Fortunately, this trick is necessary only because we are trying to do everything in one file. A better solution – is to put the definition of each class in a separate header file, and the definitions of the member functions in the corresponding .cpp files. That way, all the class definitions would be immediately visible in the .cpp files, and there would be no need to reorder classes and functions!

Conclusion

A friend function or class – is a function or class that can access the private members of another class as if it were a member of that class. This allows the friend function or class to work closely with the other class without forcing the other class to expose its private members (for example, through access functions).

Declaring friends is usually used when defining overloaded operators (which we will talk about in the next chapter), or, less commonly, when two or more classes need to interact closely with each other.

Note that in order to make a specific member function a friend, you must first see the full definition of the class that the member function belongs to.

See also

  • [[b7888]]
  • [[b4874]]
  • [[b3307]]
  • [[b5320]]
  • [[b9945]]
  • [[b9242]]
  • Virtual classes
  • Virtual destructors
  • Virtual functions

See also

avatar
22.3.2020 13:31

чтобы еще лучше понять смысл классов интерфейсов и вообще ООП, рекомендуем прочитать UML диаграммы классов
https://intellect.icu/diagramma-klassov-class-diagram-4825

Отношения классов в UML
https://intellect.icu/otnosheniya-klassov-v-uml-4301

Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Object oriented programming"

Terms: Object oriented programming