小古银的官方网站(完整教程):http://www.xiaoguyin.com/
C++入门教程视频:https://www.bilibili.com/video/av20868986/
目录
菱形继承问题
基础示例 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
继承了base
的show()
函数作为自己的show()
函数,而derived2
也是一样。然后final_derived
继承了derived1
的show()
函数,同时也继承了derived2
的show()
函数,当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
继承derived1
和derived2
之后,编译器根据虚继承多出来的内存,查到derived1
和derived2
拥有共同的基类的成员,就不会从derived1
和derived2
中继承这些,而是直接从共同的基类中继承成员,也就是说,final_derived
直接继承base
的成员,然后再继承derived1
和derived2
各自新增的成员。
这样,final_derived
就不会继承两份内存。
基础拓展
注意:如果base
的成员变量都是private
,那么不会有什么奇怪的问题。但是如果base
有protected
成员变量供派生类使用的话,就需要注意了。如果derived1
和derived2
都操作了这个保护成员变量,这样就可能导致从derived1
和derived2
继承下来的操作混乱。
#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
这种情况,derived1
和derived2
就应该各自保存一份m_count
,也就是说不应该使用虚继承;但是如果不使用虚继承,那么就会出现开头的问题。所以说虚继承不能解决菱形继承的问题。这也是其他编程语言都不支持多重继承的主要原因。
所以,使用多重继承时,应该合理且慎重地使用,这样才能设计出良好而且无BUG的代码。