C++

C++ 知识量:19 - 82 - 316

18.3 多重继承与虚继承><

多重继承- 18.3.1 -

C++支持多重继承,即一个类可以从多个基类继承。这意味着一个类可以继承多个基类的属性和方法。

多重继承的语法如下:

class DerivedClass : public BaseClass1, public BaseClass2 {  
    // DerivedClass的成员  
};

在上面的代码中,DerivedClass是从BaseClass1和BaseClass2继承而来的。通过使用逗号分隔基类列表,并指定每个基类的访问修饰符(通常为public),可以定义多重继承。

多重继承可以使类从多个不同的基类中获得特性和行为。然而,它也可能导致一些复杂性和潜在的冲突。以下是一些与多重继承相关的问题和挑战:

  1. 钻石问题(Diamond Problem):当两个基类都继承自一个共同的基类,并且在派生类中同时继承这两个基类时,会出现钻石问题。这可能导致基类的成员在派生类中存在多个副本,引发二义性和冲突。

  2. 基类之间的冲突:如果两个基类具有相同名称的成员函数或变量,派生类中的代码可能会产生二义性,因为编译器无法确定要调用哪个基类的成员。

  3. 虚基类(Virtual Inheritance):为了解决钻石问题,C++引入了虚基类的概念。通过在派生类中使用虚继承,可以确保基类在继承层次结构中只存在一个副本,从而避免二义性。虚基类的语法是在派生类的继承列表中使用virtual关键字。

尽管多重继承提供了一定的灵活性,但由于其复杂性和潜在的问题,建议谨慎使用。在设计类层次结构时,仔细考虑是否真的需要多重继承,以及如何处理可能出现的冲突和二义性。

多重继承下的类作用域- 18.3.2 -

在C++中,类的作用域是指该类的成员(变量和方法)在程序中的可见性和可访问性。当一个类继承自多个基类时,它的作用域会受到多重继承的影响。

首先,需要明确多重继承下类的作用域规则:

  1. 如果一个类从多个基类继承了同名成员,那么该成员的访问和作用域取决于访问修饰符(public、protected、private)以及派生类的继承方式(通过public、protected或private继承)。

  2. 如果多个基类有不同的访问修饰符,那么派生类中的成员访问修饰符将遵循最严格的基类修饰符。例如,如果一个基类的成员被声明为private,而其他基类的成员被声明为public,那么派生类中该成员的访问修饰符将是private。

  3. 如果多个基类有相同的访问修饰符,那么派生类中的成员将继承该修饰符。

  4. 对于虚继承,基类的副本只存在于派生类的直接基类中。这意味着,在多重继承的情况下,虚基类只会在直接基类中存在一次,不会出现多个副本。

下面是一个简单的示例,说明多重继承下类的作用域:

#include <iostream>  
using namespace std;  
  
class Base1 {  
public:  
    int x;  
};  
  
class Base2 {  
protected:  
    int y;  
};  
  
class Derived : public Base1, protected Base2 {  
public:  
    void print() {  
        cout << "x = " << x << ", y = " << y << endl; // 这里是错误的,因为y是protected成员  
    }  
};  
  
int main() {  
    Derived d;  
    d.x = 10; // 正确,x是public成员  
    // d.y = 20; // 错误,y是protected成员,不能在Derived类外部访问  
    d.print(); // 输出:x = 10, y = 0(注意y没有被初始化)  
    return 0;  
}

在上面的示例中,Derived类继承自Base1和Base2。Base1中的x成员是public的,因此在派生类Derived中可以直接访问。而Base2中的y成员是protected的,因此在派生类Derived中不能直接访问。尝试在派生类外部访问y成员是错误的。在派生类内部尝试访问y成员也是错误的,因为它是protected的。因此,在派生类中打印y的值时,其值将不会被初始化。

虚继承- 18.3.3 -

C++中的虚继承是一种特殊的继承方式,用于解决多重继承带来的二义性问题。当一个类继承自多个类,而这些类又共享共同的基类时,就会出现二义性,因为编译器不知道应该使用哪个基类的副本。虚继承通过确保只有一个基类副本存在来解决这个问题。

在C++中,使用关键字virtual来声明虚继承。下面是一个使用虚继承的示例:

class Base1 : virtual public Base {  
    // ...  
};  
  
class Base2 : virtual public Base {  
    // ...  
};  
  
class Derived : public Base1, public Base2 {  
    // ...  
};

在这个例子中,Base1和Base2都虚继承自Base。这意味着在Derived类中只有一个Base类的副本。即使Base1和Base2都继承自Base,也不会出现二义性。

需要注意的是,虚继承可能会增加内存开销,因为它需要维护额外的虚表来处理继承关系。因此,应该根据实际需求谨慎使用虚继承。

虚继承中的构造函数- 18.3.4 -

在C++中,虚继承允许一个类继承自多个类,但只有一个基类的实例。虚继承通过在派生类中共享基类的方式避免了多重继承带来的二义性问题。

在虚继承中,基类的构造函数不会被自动调用。相反,基类的构造函数需要在派生类的构造函数中进行显式调用。这是因为虚继承允许多个派生类共享同一个基类实例,因此基类的构造函数只会在第一次创建基类实例时被调用。

下面是一个使用虚继承的示例,其中包含基类和派生类的构造函数:

class Base {  
public:  
    Base() {  
        // 基类构造函数  
    }  
};  
  
class Derived1 : virtual public Base {  
public:  
    Derived1() : Base() {  
        // 派生类构造函数  
    }  
};  
  
class Derived2 : virtual public Base {  
public:  
    Derived2() : Base() {  
        // 派生类构造函数  
    }  
};  
  
class Derived3 : public Derived1, public Derived2 {  
public:  
    Derived3() : Derived1(), Derived2(), Base() {  
        // 派生类构造函数  
    }  
};

在这个例子中,Derived3继承自Derived1和Derived2,它们都虚继承自Base。在Derived3的构造函数中,必须显式调用Derived1、Derived2和Base的构造函数。这是因为Derived3同时继承自Derived1和Derived2,而它们又共享同一个Base实例。因此,在创建Derived3实例时,必须先创建Base实例。