我正在尝试以类似于Java接口的方式使用C ++抽象基类。假设我们有以下只有纯虚函数的接口类:
class Shape { virtual double area()=0; };
class Square : public Shape { virtual void setLength(double length)=0; };
class Rectangle : public Square { virtual void setWidth(double width)=0; };
我尝试以下列方式实现Square和Rectangle:
class SquareImpl : public Square { /*implementation*/ };
class RectangleImpl : public SquareImpl, Rectangle { /*implementation*/ };
RectangleImpl
继承SquareImpl
和Rectangle
以重用,例如SquareImpl::area()
。但是,当我尝试编译时,出现了两个问题:首先,SquareImpl
中的所有方法都没有得到正确的继承,我必须手动重新实现RectangleImpl::area()
和RectangleImpl::setLength()
。其次,这仍然引入了Shape
RectangleImpl
的模糊基础的钻石问题。
如果我从Square
虚拟继承Shape
,我可以编译代码,但我不认为性能会随着添加的更多派生接口而扩展。同样奇怪的是,RectangleImpl
仍然没有继承SquareImpl::setLength()
,尽管SquareImpl::area()
很好地继承了。 (忽略这里的实用性)
另一个解决方案可能是使接口彼此独立,即使Square
不从Shape
继承。但是如果我定义了带有Shape
指针的函数,那么这样做会使我无法访问Square*
中的方法。它还会使Shape
和Square
之间无法进行static_cast。
所以我的问题是,在C ++中是否有任何其他设计模式来解决接口类和实现类之间的这种并行继承,而不需要虚拟继承?
(编辑说明:上面的示例代码只是我对接口和实现之间并行继承的虚拟说明。我知道有更好的方法来实现形状,但我的问题不在于如何实现形状。)
答案 0 :(得分:3)
你所拥有的是Diamond Problem的情况,它可以在允许多重继承的任何OO语言中发生。顺便说一句,这就是为什么Java的设计者决定不进行多重继承的原因之一,并提出了接口的概念。
C ++处理钻石问题的方式是Virtual Inheritance。
而且,正如codymanix指出的那样,正方形和矩形是面向对象设计的一个非常糟糕的例子,因为就OO而言a square is not a rectangle。
多点积分。首先,你在这里所做的术语是多重继承,而不是"并行继承"。其次,在这种特殊情况下,拥有class Square
和class SquareImpl
真的没有意义。如果您认为可能有Square
的不同实现,则应该只有一个基类,它提供默认实现和虚拟函数,如果需要,可以由派生类覆盖。换句话说,您应该将Square
和SquareImpl
滚动到一个具有虚函数的类中。
你当然可以使用像Java接口这样的抽象C ++类,但大多数时候没有理由这样做。接口被添加到Java中,正是为了避免缺少多重继承。在C ++中,你可以继续使用多重继承,尽管你应该总是明智地做到这一点。
答案 1 :(得分:2)
到目前为止,您并不是第一个遇到此问题的人。请参阅A Square Is Not a Rectangle以举例说明。
答案 2 :(得分:0)
Square不是Rectangle,Rectangle不是Square。他们唯一的共同点是它们是形状。所以:
class Square : public Shape {...};
class Rectangle : public Shape {...};
他们的初始化函数不同,Square::setSide(double)
和Rectangle::setLengthAndWidth(double, double)
。你不需要* Impl类。把你的东西放进Square和Rectangle。
答案 3 :(得分:0)
我认为你应该在这里寻找虚拟继承,这样你才能在它下面只有一个形状的实例 - 从语义的角度来看这似乎是显而易见的 - 即square和rectangleimpl的形状明显是相同的形状。
不确定你提到的性能问题 - 这对我来说似乎不是问题(从某种意义上说,除了调用任何v函数之外,它们不会有额外的开销)。 我不明白为什么你不能从正方形访问setLength - 很难理解为什么你可能会遇到这种情况而你没有实现的来源。
答案 4 :(得分:0)
你的问题是矩形 - > Square-> Shape对SquareImpl一无所知,所以它不能使用这些函数来满足它的抽象函数要求。
最简单的方法是在它们如此紧密绑定时不要混合你的接口和实现继承。使RectangleImpl继承Square和Rectangle接口。如果SquareImpl和RectangleImpl复制了太多彼此的代码,请使用一个完成所有这些工作的类,并在每个实现中作为成员函数。
答案 5 :(得分:0)
重新思考了一晚,并参考了Looking for a better way than virtual inheritance in C++提供的解决方案,我提出了以下解决方案。
在这里,我将问题重新定义为更抽象,以避免我们对形状的混淆。我们有一个可以滚动的Ball
接口,一个包含Foo特定方法的FooBall
接口,以及一个FooBarBall
接口,它也是一个FooBall
,包含Foo特定和Bar具体方法。与原始问题相同,我们有FooBall
实现,我们希望将其推导出来以涵盖Bar特定方法。但继承接口和实现将引入钻石继承。
要解决这个问题,我不是直接将Foo和Bar特定方法放入派生的Ball
接口,而是将一个方法放入派生的FooBall
接口,将对象转换为{{1通过Foo
方法对象。这样,实现可以混合独立的toFoo()
和Foo
接口,而不会引入钻石继承。
尽管如此,并非所有代码都可以被删除,以便自由地从Foos中获取所有条形图。我们还必须编写Bar
,Ball
和FooBall
的独立实现,这些实现不会相互继承。但是我们可以使用复合模式来包装以不同方式实现的真实FooBarBall
和Foo
对象。这样,如果我们有很多Foo和Bar的实现,我们仍然可以消除相当多的代码。
Bar