Last Updated:

multiple inheritance in c++

multiple inheritance in c++
A class can be derived from any number of base classes. Having more than one immediate base class is called multiple inheritance.

class A { ... };
class B { ... };
class C { ... };
class D : public A, public B, private C { ... };

You can pass a pointer to a derived class to functions that expect a pointer to one of the base classes. Implementing this mechanism involves simple compilation methods to ensure that a function that expects a pointer to one base class will see a different part of the derived class than the one that a function waiting for a pointer to another base class will see. Virtual functions work as usual.


class A
  {public:
     void f1();
     virtual void g() = 0;
   };

class B
  {public:
     void f2();
     virtual void h() = 0;
   };

class C : public A, public B
  {public:
     voidg(); // Replacing A::g()
     void h(); // Replacing B::h()
   };

void f1(A *p) { p->f1(); }
void f2(B *p) { p->f2(); }

void main()
  {Cc;

    f1(&c);
    f2(&c);
    *p = &c;
    p->g(); // Correctly
    p->h(); // Error: function h is not a member of class A
    dynamic_cast<B *>(p)->h(); // Correctly
   }

A class cannot be specified as a direct base class more than once, but it can be an indirect base class more than once.

class A { ... }; class B : public A, public A { ... };Error!
class A { ... }; class B : public A { ... }; class C : public A { ... }; class D : public B, public C { ... };That's right.

Here, a class D object will have two sub-objects of class A.

A derived class and its base classes can be represented as a directed acyclic graph.

Graph

A class cannot appear twice in the list of base classes simply because each reference to it or its members would be ambiguous. This issue does not occur if the class appears twice as an indirect base class. A class D object has two class A subobjects, B::A and C::A.

Because object D has two objects A, casting (explicitly or implicitly) between pointer A and pointer to D is ambiguous and therefore prohibited.

D *pd = new D; A *pa = pd; pa = (A *)pd; pa = (B *)pd; pa = (C *)pd;Ambiguity! Still ambiguity! Cast to pointer A in object B // Cast to pointer on A in object C

However, a class can be both a direct and indirect base class.

class A { ... }; class B : public A { ... }; class C : public A { ... }; class D : public A, public B, public C { ... }; pa = pd; pa = (B *)pd; pa = (C *)pd;Can! Cast to pointer A directly in object D // Cast to pointer to A in object B // Cast to pointer to A in object C

2. Using-ads

 

Overload resolution does not cross class scope boundaries. In particular, ambiguities between functions from different base classes are not resolved based on parameter types. If using the same name in different base classes is the result of a well-thought-out decision, and you want to make choices based on parameter types, you can use using declarations to enter functions into a common scope.

class A { protected: void f(int x); };Protected function
class B1 : public A
 { public:
    void f(double x);
  };

class B2 : public A
 { public:
    void f(char x);
    using A::f;
  };
class C : public B1, public B2 { public: void f(char *s); using A::f; using B1::f; using B2::f; };The function A::f(int) has become open
void main() { C c; c.f(1); c.f(2.4); c.f('&'); c.f("abc"); }Function call A::f(int) // Function call B1::f(double) // Function call B2::f(char) // Function call C::f(char*)

The Using declaration in the class definition must refer to the members of the base class. The Using directive cannot be placed in a class definition. Using-the declaration cannot be used to access additional information. It is simply a mechanism to provide more convenient access to information to which access is in principle permitted. Using declarations, as well as access declarations, can change the access rights to the corresponding class member.

3. Virtual base classes

 

You can add the virtual keyword to the base class descriptor.


class A { ... }; class B : virtual public A { ... }; class C : virtual public A { ... }; class D : public B, public C { ... };

В этом случае класс D содержит только один экземпляр класса А. Графически это выглядит так:

Graph 2

Класс может содержать как виртуальный, так и не виртуальный базовый класс данного типа.


class A { ... }; class B : virtual public A { ... }; class C : virtual public A { ... }; class D : public A { ... }; class E : public B, public C, public D { ... };

Здесь класс E включает два экземпляра класса A: один из класса D, а другой виртуальный класс A, разделяемый классами B и C.

Graph 3
class A { ... }; class B : virtual public A { ... }; class C : virtual public A { ... }; class D : public A, public B, public C { ... };// Нельзя! Приведение к классу A неоднозначно.

When defining class functions with a virtual base class, the programmer generally cannot know whether the base class will be shared with other classes. This can present some problem when implementing algorithms that require the base class function to be called exactly once. The language guarantees that the virtual base class constructor is called only once. The constructor of the virtual base class is called (explicitly or implicitly) from the constructor of the object (the constructor of the "lowest" derived class).


class A
 {private:
    intn;
   public:
    A(int nn) : n(nn) { }
  };

class B1 : virtual public A
 {private:
    intn;
   public:
    B1(int a, int nn) : A(a), n(nn) { }
  };

class B2 : virtual public A
 {private:
    intn;
   public:
    B2(int a, int nn) : A(a), n(nn) { }
  };

class C : public B1, public B2
 {private:
    intn;
   public:
    // Initialization of a single object of the virtual base class A is required.
    // The first parameter of the constructors of classes B1 and B2 is not used.
    C(int a, int b1, int b2, int nn) : A(a), B1(0, b1), B2(0, b2), n(nn) { }
  };

class A
 {private:
    intn;
   public:
    A(int nn) : n(nn) { }
  };

class B1 : public A
 {private:
    intn;
   public:
    B1(int a, int nn) : A(a), n(nn) { }
  };

class B2 : public A
 {private:
    intn;
   public:
    B2(int a, int nn) : A(a), n(nn) { }
  };

class C : public B1, public B2
 {private:
    intn;
   public:
    // Objects of the base class A in objects of classes B1 and B2 are initialized by the constructors of these classes
    C(int a1, int a2, int b1, int b2, int nn) : B1(a1, b1), B2(a2, b2), n(nn) { }
  };

If necessary, the programmer can simulate this schema by calling the functions of the virtual base class only from the "lowest" derived class.

A derived class can replace a virtual function of its direct or indirect virtual base class. Two different classes can replace the different virtual functions of a virtual base class. In this way, several derived classes can contribute to the implementation of the interface represented in the virtual base class.


class A { public: virtual ~A() { } virtual void g(); virtual void h(); }; class B1 : virtual public A { public: void g(); }; class B2 : virtual public A { public: void h(); }; class C : public B1, public B2 { }; void main() { C c; A *p = &c;
p->g(); p->h(); }Call function B1::g // Call function B2::h

If two classes replace the same base class function, you cannot spawn a derived class from them that does not replace that function. In this case, a virtual function table cannot be created because the call to that function will be ambiguous.


class A { public: virtual ~A() { } virtual void g(); virtual void h(); }; class B1 : virtual public A { public: void g(); void h(); }; class B2 : virtual public A { public: void g(); void h(); };
class C : public B1, public B2 { public: void g(); }; void main() { C c; A *p = &c; p->g(); p->h(); }Does the C::g function replace the functions B1::g and B2::g // Okay – call the function C::g // Ambiguity – B1::h or B2::h?

4. Pointers to class members

Many classes provide simple and common interfaces that you want to use in several different ways. The exact value of each interface function is determined by the object on which it is called. Often, there is a software layer between the program that makes requests and the object that receives them. Ideally, this intermediate code should not know anything about individual operations, otherwise it would have to be modified every time the set of operations changes. Therefore, such intermediate code should simply pass to the request recipient some data representing the operations that should be invoked.

One easy way to implement this approach is to forward the string representation of the operation that you want to invoke. However, someone has to create this string and someone has to decode it to determine which operation it corresponds to. It's quite difficult and inconvenient. Integers corresponding to operations could be sent. However, although numbers are convenient for a computer, for people their meaning is not obvious. And you still need code to determine which operation to call.

The C++ language offers a means of indirectly referencing a class member. A pointer to a class member is a value that identifies a class member. You can think of it as the position of a class member in a class object, but of course the compiler takes into account the differences between data, virtual functions, non-virtual functions, and so on.

A pointer to a class member can be obtained by applying the address take operator & to a fully qualified class member name. A variable of type "pointer to a member of class X" is declared using the form X::*.

A pointer to a member of the m class is used in combination with an object. ->* and . * allow the programmer to express such a combination. p->*m binds m to the object pointed to by p and obj. *m binds m to an obj object. The result can be used according to type m. It is not possible to save the result of these operations for future use.

Naturally, if we knew which class member to use, we could do it directly without using pointers. Like pointers to regular functions, pointers to class members are used when you need to reference an object whose name is unknown. However, unlike a pointer to a variable or regular function, a pointer to a class member is not simply a pointer to memory. It corresponds more to a bias in a structure or an index in an array. Combining a pointer to a class member with an object or pointer to an object gives what identifies a particular member of a particular object.


class Base { public: virtual void open() = 0; virtual void close() = 0; void print() { std::cout << "Base: print" << std::endl; } virtual ~Base() { } }; class D1 : public Base { public: void open() { std::cout << "D1: open" << std::endl; } void close() { std::cout << "D1: close" << std::endl; } void print() { std::cout << "D1: print" << std::endl; } }; class D2 : public Base { public: void open() { std::cout << "D2: open" << std::endl; } void close() { std::cout << "D2: close" << std::endl; } void print() { std::cout << "D2: print" << std::endl; } };
typedef void (Base::*PF)(); void main() { PF pf1 = &Base::open; PF pf2 = &Base::close; PF pf3 = &Base::print; D1 d; (d.*pf1) (); (d.*pf2) (); (d.*pf3) (); Base *pb = new D2; (pb->*pf1) (); (pb->*pf2) (); (pb->*pf3) (); }Define the type for the pointer to the member function of the Base class // Remember pointers to the members of the Base // class (the open and close functions are virtual, // and the print function is not virtual). – object of class D1 // Function call D1::open() // Function call D1::close() // Function call Base::p rint() // – pointer to object of class Base (the object actually belongs to class D2) // Function call D2::open() // Function call D2::close() // Call to Base::p rint() functiondpb

5. Example

We will develop the Storable class, which allows you to write to and read from disk objects derived from it. We use it to create classes for shapes, information about which can be read from the disk and written to the disk.

Shapes.h file

#define SHAPES

class Shapes
 { protected:
    static int count;
    int color;
    int left, top, right, bottom;
    Shapes()  { count++; }

   public:
    enum {LEFT, UP, RIGHT, DOWN};
    virtual ~Shapes() { count--; }
    static int GetCount() { return count;  }
    int Left()   const { return left;   }
    int Top()    const { return top;    }
    int Right()  const { return right;  }
    int Bottom() const { return bottom; }
    virtual void Draw() = 0;
    virtual void Move(int where, const Shapes *shape) = 0;
    virtual Shapes* NewShape() = 0;
    virtual Shapes* Clone()    = 0;
  };

The Shapes file.cpp

#include "Shapes.h"

int Shapes::count = 0;

Storable.h file

#define STORABLE

#include <fstream>
using namespace std;

class Storable
 { protected:
    ifstream is;
    ofstream os;
   public:
    enum {READ, WRITE};
    Storable(const char *f1, const char *f2);
    Storable(const char *f,  int mode);
    virtual ~Storable();
    virtual int Read()  = 0;
    virtual int Write() = 0;
  };

Storable file.cpp

#include "Storable.h"

Storable::Storable(const char *f1, const char *f2)
 { is.open(f1, ios_base::in);
   if (!is.is_open())
    throw f1;
   os.open(f2, ios_base::out);
   if (!os.is_open())
    throw f2;
  }

Storable::Storable(const char *f,  int mode)
 { if (mode == READ)
    { is.open(f, ios_base::in);
      if (!is.is_open())
       throw f;
     }
   else if (mode == WRITE)
    { os.open(f, ios_base::out);
      if (!os.is_open())
       throw f;
     }
  }

Storable::~Storable()
 { if (is.is_open())
    is.close();
   if (os.is_open())
    os.close();
  }

Circle.h file*

#if !defined(SHAPES)
 #include "Shapes.h"
#endif

class Circle : public Shapes
 { private:
    int cx, cy, radius;
   public:
    Circle(int x = 0, int y = 0, int r = 0, int c = 0);
    ~Circle() { }
    void Draw();
    void Move(int where, const Shapes *shape);
  };

Triangle.h file*

#if !defined(SHAPES)
 #include "Shapes.h"
#endif

class Triangle : public Shapes
 { private:
    int x1, y1, x2, y2, x3, y3;
   public:
    Triangle(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0, int x3 = 0, int y3 = 0, int c = 0);
    ~Triangle() { }
    void Draw();
    void Move(int where, const Shapes *shape);
  };

Circle_Storable.h file

#include "Circle.h"

#if !defined(STORABLE)
 #include "Storable.h"
#endif

class Circle_Storable : public Circle, virtual public Storable
 { public:
    Circle_Storable(const char *f1, const char *f2, int x = 0, int y = 0, int r = 0, int c = 0) :
     Storable(f1, f2), Circle(x, y, r, c) { }
    Circle_Storable(const char *f,  int mode, int x = 0, int y = 0, int r = 0, int c = 0) :
      Storable(f, mode), Circle(x, y, r, c) { }
    ~Circle_Storable() { }
    int Read();
    int Write();
  };

Circle_Storable.cpp file

#include "Circle_Storable.h"

int Circle_Storable::Read()
 { if (!is.is_open())
    return 0;
   is >> cx >> cy >> radius >> color;
   left  = cx - radius; top    = cy - radius;
   right = cx + radius; bottom = cy + radius;
   return 1;
  }

int Circle_Storable::Write()
 { if (!os.is_open())
    return 0;
   os << cx << " " << cy << " " << radius << " " << color;
   return 1;
  }

Triangle_Storable.h file

#include "Triangle.h"

#if !defined(STORABLE)
 #include "Storable.h"
#endif

class Triangle_Storable : public Triangle, virtual public Storable
 { public:
    Triangle_Storable(const char *f1, const char *f2, 
                      int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0, int x3 = 0, int y3 = 0, int c = 0) :
      Storable(f1, f2), Triangle(x1, y1, x2, y2, x3, y3, c) { }
    Triangle_Storable(const char *f,  int mode, 
                      int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0, int x3 = 0, int y3 = 0, int c = 0) :
      Storable(f, mode), Triangle(x1, y1, x2, y2, x3, y3, c) { }
    ~Triangle_Storable() { }
    int Read();
    int Write();
  };

Triangle_Storable.cpp file

#include "Triangle_Storable.h"

int Max(int a, int b, int c);
int Min(int a, int b, int c);

int Triangle_Storable::Read()
 { if (!is.is_open())
    return 0;
   is >> x1 >> y1 >> x2 >> y2 >> x3 >> y3 >> color;
   left   = Min(x1, x2, x3);
   top    = Min(y1, y2, y3);
   right  = Max(x1, x2, x3);
   bottom = Max(y1, y2, y3);
   return 1;
  }

int Triangle_Storable::Write()
 { if (!os.is_open())
    return 0;
   os << x1 << " " << y1 << " " << x2 << " " << y2 << " " << x3 << " " << y3 << " " << color;
   return 1;
  }

The main file.cpp

#include "Circle_Storable.h"
#include "Triangle_Storable.h"

void main()
 { Shapes* shapes[10];

   try
    { shapes[0] = new Circle_Storable("c1.txt", Storable::WRITE, 100, 100, 30, 50);
      shapes[1] = new Triangle_Storable("t1.txt", Storable::WRITE, 0, 0, 20, 0, 0, 20, 90);
      shapes[2] = new Circle_Storable("c2.txt", Storable::WRITE, 200, 200, 50, 20);
     }
   catch(const char *s)
    { printf("Impossible to open file '%s'\n", s); return; }
   
   for(int i = 0; i < Shapes::GetCount(); i++)
    shapes[i]->Draw();
   
   for(int i = 1; i < Shapes::GetCount(); i++)
    shapes[i]->Move(Shapes::LEFT, shapes[i - 1]);
   
   for(int i = 0; i < Shapes::GetCount(); i++)
    shapes[i]->Draw();

// The array stores pointers to the Shapes class. Real objects belong to the Circle_Storable or Triangle_Storable class,
// which are classes derived from both the Shapes class and the Storable class. We don't know which class.
// (Circle_Storable or Triangle_Storable) the object belongs, but we can convert the pointer to a pointer to the Storable class,
// which, like the Shapes class, is the base for both classes. However, this can only be done while working.
// program, so the dynamic_cast type conversion operator is used, which checks whether an object belongs to a class,
// derived from the Shapes and Storable classes, and performs the transformation. If the object did not actually belong to a class,
// derived from these classes, it would be impossible to convert a pointer to the Shapes class to a pointer to the Storable class,
// and the dynamic_cast operator would return the value 0.
   for(int i = 0; i < Shapes::GetCount(); i++)
    dynamic_cast<Storable*>(shapes[i])->Write();
   
   for(int i = 0, n = Shapes::GetCount(); i < n; i++)
    delete shapes[i];
  }