【问题标题】:Problem with Inheritance继承问题
【发布时间】:2011-04-05 06:56:06
【问题描述】:

这是来自“Exceptional C++”第 24 条,解决方案,页面底部的第一个项目符号的片段:

切勿使用公共继承来实现“IS-ALMOST-A”。我见过一些程序员,甚至是经验丰富的程序员,从基类公开继承,并以保留基类语义的方式实现“大多数”被覆盖的虚函数。换句话说,在某些情况下,将 Derived 对象用作 Base 的行为方式与合理的 Base 客户端所期望的方式不同。 Robert Martin 经常引用的一个例子是从 Rectangle 类继承 Square 类的通常被误导的想法,“因为正方形就是矩形”。这在数学中可能是正确的,但在课堂上不一定是正确的。例如,假设 Rectangle 类有一个虚拟 SetWidth(int) 函数。然后 Square 设置宽度的实现也会自然地设置高度,以便对象保持正方形。然而,系统中的其他地方很可能存在与 Rectangle 对象进行多态工作的代码,并且不会期望改变宽度也会改变高度。毕竟,一般的矩形不是这样的!这是违反 LSP 的公共继承的一个很好的例子,因为派生类不提供与基类相同的语义。它违反了公共继承的关键戒律:“要求不多,承诺也不少。”

我试着检查了一下,我写道:

// Square.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
using namespace std;
class Rectangle
{
private:
 unsigned width_;
 unsigned height_;
public:
 Rectangle(const unsigned width, const unsigned height):width_(width),height_(height)
 {/*Empty body*/ }
 unsigned GetWidth()const
 {
  return width_;
 }
 unsigned GetHeight()const
 {
  return height_;
 }
 virtual void SetWidth(const unsigned width)
 {
  width_ = width;
 }
 void SetHeight(const unsigned height)
 {
  height_ = height;
 }
 virtual ~Rectangle()
 {
  cout << "~Rectangle()" << '\n';
 };
};

class Square : public Rectangle
{
 using Rectangle::SetWidth;
public:
 Square(const unsigned width):Rectangle(width,width)
 {
 }
 void SetWidth(const unsigned width)
 {

  SetWidth(width);
  SetHeight(width);
 }
 ~Square()
 {
  cout << "~Sqare()" << '\n';
 }
};

int _tmain(int argc, _TCHAR* argv[])
{
 Rectangle** a = static_cast<Rectangle**>(operator new (sizeof(Rectangle) * 2));
 a[0] = new Rectangle(10,10);
 a[1] = new Square(5);
 Rectangle* r = a[0];
 cout << r->GetHeight() << "\t" << r->GetWidth() << '\n';
 r = a[1];
 cout << r->GetHeight() << "\t" << r->GetWidth() << '\n';
 r = a[0];
 r->SetWidth(20);//here I'm setting just width for a Rectangle
 cout << r->GetHeight() << "\t" << r->GetWidth() << '\n';
delete a[1];
delete a;
     return 0;
    }

至于我从 Rectangle 继承 Square 按预期工作。那么我在哪里犯了错误并且不明白这个项目符号中所说的内容?
谢谢

【问题讨论】:

标签: c++ inheritance


【解决方案1】:

您的代码很好。项目符号的意思是有人可能会编写一些代码,这些代码取决于在 SetWidth 调用中保持不变的矩形的高度:

int old_height = r->GetHeight();
r->SetWidth(100);
assert old_height == r->GetHeight();

此代码会因您在 Square 中实现 SetWidth 而失败。

【讨论】:

  • 代码不对,Square违反了Rectangle制定的约定。
  • 代码没问题。他在任何地方都没有违反 Rectangle 签订的任何合同,因为 Rectangle 没有签订任何合同。 Rectangle::SetWidth 的规范需要像“不改变高度”这样的保证。当然,我们经常假设这一点,但我会争辩说,如果没有那个明确的规范,那是我的代码是错误的(就像这个问题的其他答案中的其他示例代码一样)。
  • 规范不一定要明确成为规范。你不能把所有事情都说清楚。
  • 我想不会。需要明确的是,我不想使用没有该保证的 Rectangle 类。但是 OP 的代码不依赖任何保证,他需要像我上面这样的代码来创建描述的继承错误。
【解决方案2】:

关键是Square 类的语义与Rectangle 类的语义不同。假设你有一个像这样的通用实用函数:

void doubleArea(Rectangle &rect) {
  rect.setWidth(rect.getWidth() * 2);
}

目的是调用该函数将使给定矩形的面积(宽 × 高)加倍。

使用派生的Square 类,您现在可以执行以下操作:

Square sq(1);
doubleArea(sq);

突然sq四倍的区域,然后是呼叫之前。您只是想将面积翻倍,但结果错误。

由于Square 的语义与Rectangle 的语义不同,因此有时对矩形做的事情对正方形不起作用。因此,通过从另一个派生一个来声明 SquareRectangle 并不是一个好主意,因为派生类无法满足基类提出的所有要求/承诺。

有关此主题的更多信息,另请参阅 C++ FAQ Lite 条目"Is a Circle a kind-of an Ellipse?"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 2012-05-11
    • 2015-02-12
    相关资源
    最近更新 更多