摘要:以下情况涉及使用多个继承来继承扩展接口和基本接口的实现。
我使用虚拟多重继承来放置以下系统:
我有一个抽象接口的层次结构:
ICommon
/ \
ISpecific1 ISpecific2
正如预期的那样,特定接口添加了与来自ICommon的共同功能相比的功能。
我还拥有实现接口的类:
- commonIMP(实现共同功能-ICommon)
- extimimp1(实现ISPECIFIC1)
- 特定IMP2(实现ISPECIFIC2)
该应用程序最终仅使用特定IMP1和特定IMP2。
但是,为了避免两次实施ICommon(在特定的IMP1,特定IMP2)中,需要常见的IMP。
这意味着,例如,特定于特定的IMP1需要对其所需的整个接口继承ISPECIFIC1,而对于实施共同部分,则需要commonimp。
但是iSpecific1和CommonIMP都继承了ICommon(一个是为了扩展接口,一个用于实现)。
这使特定的IMP1从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;
}
到目前为止,此设计为我提供了很好的服务,但这很麻烦。
而且,多重继承始终是您应该考虑替代方案的东西。
因此,我的问题是,考虑到这个系统,您能提出一个很好的选择吗?
顺便说一句 - 上面的所有类型都是所有公开的结构。这样做是为了使代码较短,请忽略它。
Summary: The case below involves using multiple inheritance for inheriting both an extended interface, and implementation for a basic interface.
I used virtual multiple inheritance to put in place the following system:
I have a hierarchy of abstract interfaces:
ICommon
/ \
ISpecific1 ISpecific2
As expected, the specific interfaces add functionality over the common functionality derived from ICommon.
I also have classes implementing the interfaces:
- CommonImp (implements the common functionality - ICommon)
- SpecificImp1 (implementing ISpecific1)
- SpecificImp2 (implementing ISpecific2)
The application eventually uses only SpecificImp1 and SpecificImp2.
But CommonImp is required in order to avoid implementing ICommon twice (in SpecificImp1, SpecificImp2).
This means that, e.g., SpecificImp1 needs to inherit ISpecific1 for the whole interface it needs to expose, and CommonImp for the implementation of the common part.
But both ISpecific1 and CommonImp inherit ICommon (one in order to extend the interface, and one for implementation).
Which makes SpecificImp1 inherit indirectly twice from ICommon.
Using virtual inheritance handled the diamond inheritance issue.
This is the minimal reproducible code example:
#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;
}
This design serves me well so far, but it's quite cumbersome.
And multiple inheritance is always something you should consider alternatives for.
So, my question is, given this system, can you suggest a good alternative?
BTW - All types above are structs with everything public. This was done to make the code shorter, please ignore it.
发布评论
评论(2)
作为对答案中第 1 点的修改,您不必使
CommonImp
成为成员,也不必重复任何内容:这解决了一般要求。不需要所需的特定接口的虚拟继承,没有菱形继承。
但是,在上面的代码中,特定接口并未强制要求特定实现也必须提供 ICommon 接口。但它是通过各种用法间接强制执行的:通过在 1. 和 2. 中调用
DoCommon()
以及在 3. 中将特定对象的地址 ob 转换为ICommon*
以及4. 因此,最简单的解决方案就是保持原样。我们可以(在一定程度上)通过要求成员函数
getICommonP
来强制继承ICommon
,甚至在特定的接口中,该函数返回一个ICommon
指针(参见下面的代码)。从ICommon
继承的特定实现只会返回this
。如果未实现此抽象成员函数,编译器将抛出错误。此功能的另一个好处是可以从特定接口转换为通用接口(5. 和 6.)。具体实现可以直接转换(3.和4.),因为它继承自ICommon
。请参阅标有“// 添加”的行。更自然的成员函数是接口中的转换运算符
虚拟运算符 ICommon&()
,而不是virtual ICommon* getICommonP()
,但某些编译器会在以下情况下发出警告:您的实现也继承自同一个类,因为在这种情况下,到基类引用的转换是自动完成的,无需调用显式转换运算符成员函数。旁注:特定接口与答案中第 1 点中的解决方案兼容(公共接口转发除外):
getICommonP()
函数将返回一个指向ICommonImp
成员的指针。实现可以决定如何实现和提供公共接口 - 继承它或将公共实现保留为成员变量。是将特定接口制作为模板并从它们继承:
如果您不喜欢添加的成员函数,则强制从特定接口中的接口
ICommon
继承特定实现的另一种方法模板 Ispecific2
可以测试(或要求)T
是否继承自ICommon
和ISpecific2
。这将进入您答案中第 3 点的方向。
由于您现在没有一个
Ispecific2
,而是许多ISpecific2<>
(因为它是一个模板) - 并且您经常需要一个接口,因此您可以拥有所有模板化的类ISpecific2Templ<>
继承自相同的实际特定接口ISpecific2
。As modification of point 1 in your answer, you do not have to make
CommonImp
a member nor duplicate anything:This solves the general requirement. No virtual inheritance for the specific interfaces needed, no diamond inheritance.
However, in the code above, it is not enforced by the specific interfaces that the specific implementations also have to provide the
ICommon
interface. But it is indirectly enforced by various usages: By callingDoCommon()
in 1. and 2. and by converting the address ob the specific objects toICommon*
in 3. and 4. So the simplest solution would be to leave it at that.We can (to a degree) enforce the inheritance of
ICommon
even within the specific interfaces by demanding a member functiongetICommonP
, which returns anICommon
pointer (see code below). The specific implementations inheriting fromICommon
would just returnthis
. The compiler would throw an error, if this abstract member function is not implemented. This function has the added benefit of making it possible to convert from a specific interface to the common interface (5. and 6.). The specific implementation can be directly converted (3. and 4.), because it inherits fromICommon
. See lines marked with '// added'.A more natural member function would be the conversion operator
virtual operator ICommon&()
within the interfaces instead ofvirtual ICommon* getICommonP()
, but some compilers deliver a warning, when your implementations also inherit from the same class, because the conversion to references to base classes is done automatically in this case without calling the explicit conversion operator member function.On a side note: The specific interfaces are compatible with the solution in point 1 in your answer (except the common interface forwarding): Instead of a pointer to the class itself ('this') the
getICommonP()
functions would return a pointer to theICommonImp
member. The implementation could decide, how to implement and provide the common interface - inheriting from it or keeping the common implementation as member variable.If you do not like an added member function, an alternative for enforcing inheritance of the specific implementations from the interface
ICommon
within the specific interfaces would be to make the specific interfaces into templates and inheriting from them with:Then
template<class T> Ispecific2<T>
could test (or require), whetherT
inherits fromICommon
and fromISpecific2<T>
.This would go into the direction of point 3 in your answer.
As you now have not one
Ispecific2
, but manyISpecific2<>
(because of it being a template) - and you often need one interface, you could have all the templated classesISpecific2Templ<>
inherit from the same actual specific interfaceISpecific2
.这是我从评论中得到的想法的摘要,以防万一对任何人都充满信心。
有一些替代方法可以摆脱多种继承:
最简单的选择:
特定IMP1
,extimimp2
将仅继承ispecific1
,并且它们将以成员的形式保留commonimp
。然后,他们将通过委派
commonimp
成员。唯一的缺点是需要在
extimimp1
,extile> extile> extimimp2
中复制ICommon实现。(尽管这是一个微不足道的重复 - 只是向成员转发)。
使用类型 - 呼吸技术来实现无继承的多态性。
这里的一些示例: c ++'type'type of of type'
一个有趣的视频:无遗传的多态性
缺点是很多建造板代码。
其他基于模板的技术。
也许使用CRTP模式(
我仍然必须考虑这个方向。
感谢您的想法。
Here's a summary of the ideas I got from the comments, in case it's ever usuefull to anybody.
There are some alternatives that will rid me of the multiple inheritance:
Simplest alternative:
SpecificImp1
,SpecificImp2
will inherit onlyISpecific1
, and they'll keepCommonImp
as a member.Then they will implement all methods of
ICommon
by delegating to theCommonImp
member.The only downside is the need to duplicate the ICommon implementation in
SpecificImp1
,SpecificImp2
(although it's a trivial duplication - just forwardind to the member).
Use type-erasure techniques to achieve polymorphism without inheritance.
Some examples here: C++ 'Type Erasure' Explained
An interesting video: polymorphism without inheritance
The downside is quite a lot of builerplate code.
Other template based techniques.
Maybe use the CRTP pattern (Curiously recurring template pattern),
To achieve sort of compile time polymorphism.
I still have to think about this direction.
Thanks for the ideas.