C++多重继承同名父类成员访问与虚继承钻石问题问询
核心问题成因
你当前的输出不符合预期本质是虚继承的设计特性导致的:
- 当你使用
virtual公开继承ClapTrap时,整个继承树中只会生成唯一一份ClapTrap类实例,对应的_hitpoints是该实例的唯一成员,不存在ScravTrap和FragTrap各自独立的_hitpoints副本。 - 实例化
DiamondTrap时构造顺序为:先调用虚基类ClapTrap构造函数将_hitpoints设为0 → 按继承顺序调用ScravTrap构造函数将_hitpoints改为1 → 调用FragTrap构造函数将_hitpoints改为2 → 最后执行DiamondTrap构造函数。此时唯一的_hitpoints已经被覆盖为2,不管加不加类作用域前缀访问的都是同一个变量,所以输出两个2。
至于你添加using ScravTrap::_hitpoints后输出不变的原因:using声明仅修改名字查找的默认优先级,不会改变该名字对应的实际内存地址,它仍然指向唯一的虚基类_hitpoints成员,所以值不会变化。
需求实现方案
你要求「仅调用一次ClapTrap构造函数,同时能分别访问ScravTrap和FragTrap各自的_hitpoints」,需要把两类成员拆分存放:
- 所有子类共享的公共成员放在虚基类
ClapTrap中,保证仅生成一份、仅构造一次 ScravTrap、FragTrap各自需要独立维护的成员(比如_hitpoints)放在派生类自身的定义中,不要继承自虚基类
修改后代码示例:
#include <iostream> class ClapTrap { public: ClapTrap(void) { // 仅初始化所有子类共享的公共成员 _commonAttr = 0; } ~ClapTrap() { } protected: // 所有子类共享的公共属性,仅存一份 int _commonAttr; }; class ScravTrap : public virtual ClapTrap { public: ScravTrap(void) { // 自己类独有的_hitpoints,和FragTrap的互不干扰 _hitpoints = 1; } ~ScravTrap() { } protected: int _hitpoints; }; class FragTrap : public virtual ClapTrap { public: FragTrap(void) { // 自己类独有的_hitpoints _hitpoints = 2; } ~FragTrap() { } protected: int _hitpoints; }; class DiamondTrap : public ScravTrap, public FragTrap { public: DiamondTrap(void) { std::cout << ScravTrap::_hitpoints << std::endl; std::cout << FragTrap::_hitpoints << std::endl; } ~DiamondTrap() { } }; int main() { DiamondTrap d; // 输出为预期的1、2,且ClapTrap仅构造一次 }
如果你不要求ClapTrap只构造一次,也可以直接去掉两个派生类继承时的virtual关键字,此时会生成两份ClapTrap副本,也能得到1、2的输出,但不符合你补充的仅构造一次的要求。
分父类继承不同成员的实现
可以实现从不同父类继承不同成员:
- 若两个父类的成员名不同,直接正常访问即可,不会冲突
- 若两个父类存在同名成员,你可以通过
using声明指定默认使用哪个父类的成员,需要访问另一个时加上类作用域前缀即可。
示例:
class ScravTrap : public virtual ClapTrap { protected: int _energyPoints = 10; int _attackDamage = 20; }; class FragTrap : public virtual ClapTrap { protected: int _energyPoints = 30; int _attackDamage = 40; }; class DiamondTrap : public ScravTrap, public FragTrap { public: // 指定默认访问ScravTrap的_energyPoints,FragTrap的_attackDamage using ScravTrap::_energyPoints; using FragTrap::_attackDamage; void test() { std::cout << _energyPoints << std::endl; // 输出10,默认用ScravTrap的 std::cout << FragTrap::_energyPoints << std::endl; // 加前缀访问FragTrap的,输出30 std::cout << _attackDamage << std::endl; // 输出40,默认用FragTrap的 std::cout << ScravTrap::_attackDamage << std::endl; // 加前缀访问ScravTrap的,输出20 } };
内容的提问来源于stack exchange,提问作者kubo
相关产品推荐
相关产品推荐

