• Index

虚继承

Reads: 1

菱形继承问题

多重继承

基础示例 1

多重继承可能会造成菱形继承的问题:

#include <iostream> // std::cout std::endl

class base
{
public:
    void show(void) const noexcept;
};

class derived1 : public base
{
};

class derived2 : public base
{
};

class final_derived : public derived1, public derived2
{
};

int main(void)
{
    final_derived object;
    // object.show(); // 去掉开头注释编译将报错
    return 0;
}

void base::show(void) const noexcept
{
    std::cout << "显示" << std::endl;
}

基础讲解 2

当去掉上面代码的注释后,编译将会报错。我们知道derived1继承了baseshow()函数作为自己的show()函数,而derived2也是一样。然后final_derived继承了derived1show()函数,同时也继承了derived2show()函数,当object调用show()函数时,由于编译器不知道你想调用那个show()函数,所以就报错了。

基础示例 2

可以使用以下方法解决:

#include <iostream> // std::cout std::endl

class base
{
public:
    void show(void) const noexcept;
};

class derived1 : public base
{
};

class derived2 : public base
{
};

class final_derived : public derived1, public derived2
{
};

int main(void)
{
    final_derived object;
    object.derived1::show();
    object.derived2::show();
    return 0;
}

void base::show(void) const noexcept
{
    std::cout << "显示" << std::endl;
}

基础讲解 2

使用之间教程讲解的方法,在函数前面加上基类以说明调用的是哪一个函数。虽然这个方法可以解决上面的错误,但是这样写并不优雅,更主要的是,假设base有4个long long成员变量,也就是占用32字节,那么final_derived对象就占用了两倍,也就是64字节。

虚继承

虚继承可以解决上面的部分问题,但不能解决全部问题,所以使用多重继承时需要特别小心。

虚继承

基础示例

#include <iostream> // std::cout std::endl

class base
{
public:
    void show(void) const noexcept;
};

class derived1 : virtual public base
{
};

class derived2 : virtual public base
{
};

class final_derived : public derived1, public derived2
{
};

int main(void)
{
    final_derived object;
    object.show();
    return 0;
}

void base::show(void) const noexcept
{
    std::cout << "显示" << std::endl;
}

基础讲解

上面代码可以正常运行并输出正确结果。

上面代码中,derived1继承base使用虚继承,derived2继承base使用虚继承,final_derived则不需要。

虚继承使派生类除了继承基类成员作为自己的成员之外,内部还会有一份内存来保存哪些是基类的成员。当final_derived继承derived1derived2之后,编译器根据虚继承多出来的内存,查到derived1derived2拥有共同的基类的成员,就不会从derived1derived2中继承这些,而是直接从共同的基类中继承成员,也就是说,final_derived直接继承base的成员,然后再继承derived1derived2各自新增的成员。

这样,final_derived就不会继承两份内存。

基础拓展

注意:如果base的成员变量都是private,那么不会有什么奇怪的问题。但是如果baseprotected成员变量供派生类使用的话,就需要注意了。如果derived1derived2都操作了这个保护成员变量,这样就可能导致从derived1derived2继承下来的操作混乱。

#include <iostream> // std::cout std::endl
#include <string> // std::u32string

class base
{
public:
    // 获取字符数
    std::size_t size(void) const noexcept;

protected:
    std::size_t m_count;
};

class derived1 : virtual public base
{
public:
    derived1(const std::u32string &text);
};

class derived2 : virtual public base
{
public:
    derived2(const std::u32string &text);
};

class final_derived : public derived1, public derived2
{
public:
    final_derived(void);
};

int main(void)
{
    final_derived object;
    std::cout << "字符数是" << object.size() << std::endl;
    return 0;
}

derived1::derived1(const std::u32string &text)
{
    m_count = text.size(); // 保存字符串字符数
}

derived2::derived2(const std::u32string &text)
{
    m_count = text.size(); // 保存字符串字符数
}

final_derived::final_derived(void)
    : derived1(U"口也*啦") // 4个字符
    , derived2(U"梁非凡") // 3个字符
{
}

std::size_t base::size(void) const noexcept
{
    return m_count;
}

输出结果:

字符数是3

这种情况,derived1derived2就应该各自保存一份m_count,也就是说不应该使用虚继承;但是如果不使用虚继承,那么就会出现开头的问题。所以说虚继承不能解决菱形继承的问题。这也是其他编程语言都不支持多重继承的主要原因。

所以,使用多重继承时,应该合理且慎重地使用,这样才能设计出良好而且无BUG的代码。


Comments

Make a comment

  • Index

WARNING: You are using an old browser that does not support HTML5. Please choose a modern browser (Chrome / Microsoft Edge / Firefox / Sarafi) to get a good experience.