在这种情况下,您能建议一个好的多重继承替代方案吗?
Can you suggest an good alternative for multiple inheritance in this case?
总结:下面的案例涉及使用多重继承来继承扩展接口和基本接口的实现。
我使用虚拟多重继承建立了以下系统:
我有一个抽象接口层次结构:
ICommon
/ \
ISpecific1 ISpecific2
正如预期的那样,特定接口在派生自 ICommon 的通用功能之上添加了功能。
我还有 类 实现接口:
- CommonImp(实现通用功能 - ICommon)
- SpecificImp1(实现 ISpecific1)
- SpecificImp2(实现 ISpecific2)
应用程序最终只使用了 SpecificImp1 和 SpecificImp2。
但是需要 CommonImp 以避免实现 ICommon 两次(在 SpecificImp1、SpecificImp2 中)。
这意味着,例如,SpecificImp1 需要为它需要公开的整个接口继承 ISpecific1,并为公共部分的实现继承 CommonImp。
但是ISpecific1和CommonImp都继承了ICommon(一个为了扩展接口,一个为了实现)。
这使得 SpecificImp1 间接继承了 ICommon 两次。
使用虚拟继承解决了菱形继承问题。
这是最小的可重现代码示例:
#include <iostream>
struct ICommon
{
virtual void DoCommon() = 0;
};
struct ISpecific1 : public virtual ICommon
{
virtual void DoSpecific1() = 0;
};
struct ISpecific2 : public virtual ICommon
{
virtual void DoSpecific2() = 0;
};
struct CommonImp : public virtual ICommon
{
virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};
struct SpecificImp1 : public ISpecific1, public CommonImp
{
virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};
struct SpecificImp2 : public ISpecific2, public CommonImp
{
virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};
int main()
{
SpecificImp1 s1;
s1.DoCommon();
s1.DoSpecific1();
SpecificImp2 s2;
s2.DoCommon();
s2.DoSpecific2();
return 0;
}
到目前为止,这个设计对我来说很好,但它很麻烦。
多重继承始终是您应该考虑的替代方案。
所以,我的问题是,鉴于这个系统,你能推荐一个好的替代方案吗?
顺便说一句 - 以上所有类型都是包含所有内容的结构 public。这样做是为了让代码更短,请忽略。
这是我从评论中得到的想法的总结,以防对任何人有用。
有一些替代方法可以让我摆脱多重继承:
最简单的选择:
SpecificImp1
、SpecificImp2
将仅继承 ISpecific1
,并且他们将保留 CommonImp
作为成员。
然后他们将通过委托给 CommonImp
成员来实现 ICommon
的所有方法。
唯一的缺点是需要在 SpecificImp1
、SpecificImp2
中复制 ICommon 实现
(虽然这是一个微不足道的重复 - 只是转发给成员)。
使用type-erasure技术实现无继承多态
这里有一些例子:C++ 'Type Erasure' Explained
一个有趣的视频:polymorphism without inheritance
缺点是构建代码很多。
其他基于模板的技术。
也许使用 CRTP 模式 (Curiously recurring template pattern),
实现某种编译时多态性。
这个方向我还要考虑
谢谢你的想法。
作为对您答案中第 1 点的修改,您不必使 CommonImp
成为成员,也不必复制任何内容:
#include <iostream>
#include <vector>
struct ICommon
{
virtual void DoCommon() = 0;
};
struct ISpecific1
{
virtual void DoSpecific1() = 0;
};
struct ISpecific2
{
virtual void DoSpecific2() = 0;
};
struct CommonImp : public ICommon
{
virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};
struct SpecificImp1 : public ISpecific1, public CommonImp
{
virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};
struct SpecificImp2 : public ISpecific2, public CommonImp
{
virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};
int main()
{
SpecificImp1 si1;
SpecificImp2 si2;
si1.DoCommon(); // 1.
si2.DoCommon(); // 2.
{ // begin new scope to make sure the addresses stay valid
std::vector<ICommon*> imps;
imps.push_back(&si1); // 3.
imps.push_back(&si2); // 4.
}
return 0;
}
这解决了一般要求。没有针对所需特定接口的虚拟继承,没有钻石继承。
但是,在上面的代码中,具体的实现也必须提供ICommon
接口,并不是由具体的接口强制执行的。但它是通过各种用法间接强制执行的:通过在 1. 和 2. 中调用 DoCommon()
并在 3. 和 4. 中将特定对象的地址 ob 转换为 ICommon*
。所以最简单的解决方案是就这样吧。
我们可以(在一定程度上)通过要求成员函数 getICommonP
,甚至在特定的 接口 中强制执行 ICommon
的继承,returns 是一个 ICommon
指针(见下面的代码)。从 ICommon
继承的具体实现只是 return this
。如果未实现此抽象成员函数,编译器将抛出错误。此函数的额外好处是可以将特定接口转换为公共接口(5. 和 6.)。具体实现可以直接转换(3.和4.),因为继承自ICommon
。请参阅标有“//已添加”的行。
#include <iostream>
#include <vector>
struct ICommon
{
virtual void DoCommon() = 0;
};
struct ISpecific1
{
virtual ICommon* getICommonP() = 0; // added
virtual void DoSpecific1() = 0;
};
struct ISpecific2
{
virtual ICommon* getICommonP() = 0; // added
virtual void DoSpecific2() = 0;
};
struct CommonImp : public ICommon
{
virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};
struct SpecificImp1 : public ISpecific1, public CommonImp
{
virtual ICommon* getICommonP() override { return this; } // added
virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};
struct SpecificImp2 : public ISpecific2, public CommonImp
{
virtual ICommon* getICommonP() override { return this; } // added
virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};
int main()
{
SpecificImp1 si1;
SpecificImp2 si2;
{
std::vector<ICommon*> imps;
imps.push_back(&si1); // 3.
imps.push_back(&si2); // 4.
ISpecific1* si1p = &si1;
ISpecific2* si2p = &si2;
imps.push_back(si1->getICommonP()); // added 5.
imps.push_back(si2->getICommonP()); // added 6.
}
return 0;
}
更自然的成员函数是接口中的转换运算符 virtual operator ICommon&()
而不是 virtual ICommon* getICommonP()
,但是当您的实现也继承自相同的 class 时,一些编译器会发出警告],因为在这种情况下,对基 classes 的引用的转换是自动完成的,无需调用显式转换运算符成员函数。
附带说明:特定接口与您答案中第 1 点的解决方案兼容(公共接口转发除外):而不是指向 class 本身的指针('this' ) getICommonP()
函数将 return 指向 ICommonImp
成员的指针。实现可以决定如何实现和提供通用接口——从它继承或将通用实现保留为成员变量。
如果您不喜欢添加的成员函数,在特定接口中强制从接口 ICommon
继承特定实现的替代方法是将特定接口制作成模板并从它们继承:
struct SpecificImp2 : public ISpecific2<SpecificImp2>, public CommonImp
然后template<class T> Ispecific2<T>
可以测试(或要求)T
是否继承自ICommon
和ISpecific2<T>
。
这将进入您答案中第 3 点的方向。
因为你现在没有一个 Ispecific2
,而是很多 ISpecific2<>
(因为它是一个模板)——而且你经常需要一个界面,你可以拥有所有的模板 classes ISpecific2Templ<>
继承自相同的实际特定接口 ISpecific2
。
总结:下面的案例涉及使用多重继承来继承扩展接口和基本接口的实现。
我使用虚拟多重继承建立了以下系统:
我有一个抽象接口层次结构:
ICommon
/ \
ISpecific1 ISpecific2
正如预期的那样,特定接口在派生自 ICommon 的通用功能之上添加了功能。
我还有 类 实现接口:
- CommonImp(实现通用功能 - ICommon)
- SpecificImp1(实现 ISpecific1)
- SpecificImp2(实现 ISpecific2)
应用程序最终只使用了 SpecificImp1 和 SpecificImp2。 但是需要 CommonImp 以避免实现 ICommon 两次(在 SpecificImp1、SpecificImp2 中)。
这意味着,例如,SpecificImp1 需要为它需要公开的整个接口继承 ISpecific1,并为公共部分的实现继承 CommonImp。 但是ISpecific1和CommonImp都继承了ICommon(一个为了扩展接口,一个为了实现)。 这使得 SpecificImp1 间接继承了 ICommon 两次。 使用虚拟继承解决了菱形继承问题。
这是最小的可重现代码示例:
#include <iostream>
struct ICommon
{
virtual void DoCommon() = 0;
};
struct ISpecific1 : public virtual ICommon
{
virtual void DoSpecific1() = 0;
};
struct ISpecific2 : public virtual ICommon
{
virtual void DoSpecific2() = 0;
};
struct CommonImp : public virtual ICommon
{
virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};
struct SpecificImp1 : public ISpecific1, public CommonImp
{
virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};
struct SpecificImp2 : public ISpecific2, public CommonImp
{
virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};
int main()
{
SpecificImp1 s1;
s1.DoCommon();
s1.DoSpecific1();
SpecificImp2 s2;
s2.DoCommon();
s2.DoSpecific2();
return 0;
}
到目前为止,这个设计对我来说很好,但它很麻烦。 多重继承始终是您应该考虑的替代方案。
所以,我的问题是,鉴于这个系统,你能推荐一个好的替代方案吗?
顺便说一句 - 以上所有类型都是包含所有内容的结构 public。这样做是为了让代码更短,请忽略。
这是我从评论中得到的想法的总结,以防对任何人有用。
有一些替代方法可以让我摆脱多重继承:
最简单的选择:
SpecificImp1
、SpecificImp2
将仅继承ISpecific1
,并且他们将保留CommonImp
作为成员。 然后他们将通过委托给CommonImp
成员来实现ICommon
的所有方法。唯一的缺点是需要在
SpecificImp1
、SpecificImp2
中复制 ICommon 实现 (虽然这是一个微不足道的重复 - 只是转发给成员)。使用type-erasure技术实现无继承多态
这里有一些例子:C++ 'Type Erasure' Explained
一个有趣的视频:polymorphism without inheritance
缺点是构建代码很多。
其他基于模板的技术。
也许使用 CRTP 模式 (Curiously recurring template pattern), 实现某种编译时多态性。
这个方向我还要考虑
谢谢你的想法。
作为对您答案中第 1 点的修改,您不必使 CommonImp
成为成员,也不必复制任何内容:
#include <iostream>
#include <vector>
struct ICommon
{
virtual void DoCommon() = 0;
};
struct ISpecific1
{
virtual void DoSpecific1() = 0;
};
struct ISpecific2
{
virtual void DoSpecific2() = 0;
};
struct CommonImp : public ICommon
{
virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};
struct SpecificImp1 : public ISpecific1, public CommonImp
{
virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};
struct SpecificImp2 : public ISpecific2, public CommonImp
{
virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};
int main()
{
SpecificImp1 si1;
SpecificImp2 si2;
si1.DoCommon(); // 1.
si2.DoCommon(); // 2.
{ // begin new scope to make sure the addresses stay valid
std::vector<ICommon*> imps;
imps.push_back(&si1); // 3.
imps.push_back(&si2); // 4.
}
return 0;
}
这解决了一般要求。没有针对所需特定接口的虚拟继承,没有钻石继承。
但是,在上面的代码中,具体的实现也必须提供ICommon
接口,并不是由具体的接口强制执行的。但它是通过各种用法间接强制执行的:通过在 1. 和 2. 中调用 DoCommon()
并在 3. 和 4. 中将特定对象的地址 ob 转换为 ICommon*
。所以最简单的解决方案是就这样吧。
我们可以(在一定程度上)通过要求成员函数 getICommonP
,甚至在特定的 接口 中强制执行 ICommon
的继承,returns 是一个 ICommon
指针(见下面的代码)。从 ICommon
继承的具体实现只是 return this
。如果未实现此抽象成员函数,编译器将抛出错误。此函数的额外好处是可以将特定接口转换为公共接口(5. 和 6.)。具体实现可以直接转换(3.和4.),因为继承自ICommon
。请参阅标有“//已添加”的行。
#include <iostream>
#include <vector>
struct ICommon
{
virtual void DoCommon() = 0;
};
struct ISpecific1
{
virtual ICommon* getICommonP() = 0; // added
virtual void DoSpecific1() = 0;
};
struct ISpecific2
{
virtual ICommon* getICommonP() = 0; // added
virtual void DoSpecific2() = 0;
};
struct CommonImp : public ICommon
{
virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};
struct SpecificImp1 : public ISpecific1, public CommonImp
{
virtual ICommon* getICommonP() override { return this; } // added
virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};
struct SpecificImp2 : public ISpecific2, public CommonImp
{
virtual ICommon* getICommonP() override { return this; } // added
virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};
int main()
{
SpecificImp1 si1;
SpecificImp2 si2;
{
std::vector<ICommon*> imps;
imps.push_back(&si1); // 3.
imps.push_back(&si2); // 4.
ISpecific1* si1p = &si1;
ISpecific2* si2p = &si2;
imps.push_back(si1->getICommonP()); // added 5.
imps.push_back(si2->getICommonP()); // added 6.
}
return 0;
}
更自然的成员函数是接口中的转换运算符 virtual operator ICommon&()
而不是 virtual ICommon* getICommonP()
,但是当您的实现也继承自相同的 class 时,一些编译器会发出警告],因为在这种情况下,对基 classes 的引用的转换是自动完成的,无需调用显式转换运算符成员函数。
附带说明:特定接口与您答案中第 1 点的解决方案兼容(公共接口转发除外):而不是指向 class 本身的指针('this' ) getICommonP()
函数将 return 指向 ICommonImp
成员的指针。实现可以决定如何实现和提供通用接口——从它继承或将通用实现保留为成员变量。
如果您不喜欢添加的成员函数,在特定接口中强制从接口 ICommon
继承特定实现的替代方法是将特定接口制作成模板并从它们继承:
struct SpecificImp2 : public ISpecific2<SpecificImp2>, public CommonImp
然后template<class T> Ispecific2<T>
可以测试(或要求)T
是否继承自ICommon
和ISpecific2<T>
。
这将进入您答案中第 3 点的方向。
因为你现在没有一个 Ispecific2
,而是很多 ISpecific2<>
(因为它是一个模板)——而且你经常需要一个界面,你可以拥有所有的模板 classes ISpecific2Templ<>
继承自相同的实际特定接口 ISpecific2
。