类多重继承同一基类引发歧义及代码复用疑问
咱们先把你的两个疑问拆解清楚,再给你几个能解决问题的实用方案~
一、你的当前思路是否正确?这种继承方式确实不可行
你判断得完全没错,EvenMoreExtraSauce编译失败的核心问题就是重复非虚继承导致的基类歧义。当你同时通过ExtraSauce间接继承SwapMethodProvider<int, double>,又直接继承同一个实例化后的基类时,C++会给这两条继承路径分别创建一个独立的SwapMethodProvider<int, double>子对象。这时候你要是调用EvenMoreExtraSauce的Swap方法,编译器根本搞不清该用哪一个基类子对象的版本——这就是典型的多继承歧义问题。所以你现在的这种写法确实走不通。
二、为什么多层继承场景下这种方式支持不佳?
这本质是C++多继承的默认规则决定的:
- C++默认的继承是非虚继承,每一条继承路径都会单独实例化一个基类子对象,不会共享同一个实例。这么设计是为了避免虚继承带来的额外运行时开销(比如虚基类表、成员访问的间接成本)。
- 你的需求是让同一个“方法提供类”能被多次继承但只保留一份实例,这刚好和非虚继承的默认行为冲突。如果不用虚继承,多次继承同一个基类类型就会产生重复的子对象,进而引发歧义。
三、解决方法:实现无歧义的方法复用
既然你想一次性写好方法,让所有类都能继承复用,哪怕多次出现也不冲突,这里有三个靠谱的方案:
方案1:用虚继承消除歧义
把继承改成虚继承,这样无论通过多少条路径继承,SwapMethodProvider<int, double>只会被实例化一次,自然就不会有歧义了:
template< typename... DataMembers > class SwapMethodProvider { public: inline void Swap( SwapMethodProvider& other ); private: std::tuple< DataMembers... > member_dataMembers; }; template< typename... DataMembers > inline void SwapMethodProvider< DataMembers... >::Swap( SwapMethodProvider& other ) { std::swap( member_dataMembers, other.member_dataMembers ); } // 关键:用virtual关键字虚继承 class ExtraSauce : public virtual SwapMethodProvider< int, double > {}; class EvenMoreExtraSauce : public ExtraSauce, public virtual SwapMethodProvider< int, double > {};
不过要注意:虚继承会带来一点点运行时开销,而且继承链上的所有类都得用虚继承,不然还是会出问题。
方案2:用CRTP实现Mixin(更灵活)
CRTP(奇异递归模板模式)是C++里实现Mixin(混入类)的常用技巧,能完美避免继承歧义,还能让方法更贴合子类:
// 第一个模板参数是子类自身类型 template< typename Derived, typename... DataMembers > class SwapMethodProvider { public: inline void Swap( Derived& other ) { // 直接访问子类的成员变量(需要把子类设为友元) std::swap( static_cast<Derived*>(this)->member_dataMembers, static_cast<Derived*>(&other)->member_dataMembers ); } }; // 子类继承时传入自身类型 class ExtraSauce : public SwapMethodProvider<ExtraSauce, int, double> { // 让基类能访问私有成员 friend class SwapMethodProvider<ExtraSauce, int, double>; private: std::tuple<int, double> member_dataMembers; }; // 即使同时继承父类和Mixin,也不会有歧义(因为模板实例不同) class EvenMoreExtraSauce : public ExtraSauce, public SwapMethodProvider<EvenMoreExtraSauce, int, double> { friend class SwapMethodProvider<EvenMoreExtraSauce, int, double>; private: std::tuple<int, double> member_dataMembers; };
这种方式下,每个子类对应的SwapMethodProvider是不同的模板实例(因为第一个模板参数是子类本身),所以完全不会有歧义。而且Swap方法可以直接操作子类的成员,灵活性更高。
方案3:用自由函数版Swap(更符合C++风格)
其实C++标准库推荐用自由函数实现swap,而不是成员函数。你可以在类的命名空间里定义自定义swap,让ADL(参数依赖查找)自动找到它:
class ExtraSauce { public: // 可以留一个成员swap,但不是必须的 void swap(ExtraSauce& other) { std::swap(member_dataMembers, other.member_dataMembers); } private: std::tuple<int, double> member_dataMembers; // 让自由函数能访问私有成员 friend void swap(ExtraSauce&, ExtraSauce&); }; // 自由函数版swap void swap(ExtraSauce& a, ExtraSauce& b) { a.swap(b); } class EvenMoreExtraSauce : public ExtraSauce { public: void swap(EvenMoreExtraSauce& other) { // 先调用父类的swap static_cast<ExtraSauce&>(*this).swap(static_cast<ExtraSauce&>(other)); // 再交换自身的成员 std::swap(member_dataMembers, other.member_dataMembers); } private: std::tuple<int, double> member_dataMembers; friend void swap(EvenMoreExtraSauce&, EvenMoreExtraSauce&); }; void swap(EvenMoreExtraSauce& a, EvenMoreExtraSauce& b) { a.swap(b); }
这种方式不需要继承,完全基于自由函数,符合C++的设计惯例,也不会有任何继承歧义的问题。
内容的提问来源于stack exchange,提问作者rrrrTTTrrrr

