3

让我们有以下课程

class Animal
{
  public: 
    virtual short getFeet() = 0;
    virtual void setFeet(short feet) = 0;
};

和 2 个派生类:

class Bird : public Animal
{
    short flying_speed; 
  public:
    virtual short getFeet() { return 2; }  // always 2 feet
    virtual void setFeet(short feet_) { };
    virtual short getFlyingSpeed() { return flying_speed; }
};

class Mammal : public Animal 
{
    short feet; // whale: 0 feet, human: 2 feet, others: 4 feet
  public:
    virtual short getFeet() { return feets; }
    virtual void setFeet(short feet_) { feet = feet_ };
};

A)关于对象模型的问题:

让我们专注于财产的脚。如何处理属性 feet 和方法 getFeet() 和 setFeet()。

属于同一类的一些动物的足量不同,因此它们使用属性足和方法getFeet()并且setFeet()是常见的。

有些动物有相同数量的脚,所以它们不使用自己的属性feet。该方法getFeet()返回一个常量,该方法setFeet()不做任何事情。

这个模型是正确的还是推荐与可变脚相关的任何更改(有很多动物没有脚)?

B)关于多态性的问题

一类具有某些特定特征;例如飞翔的鸟。所以问他们以什么速度飞行是有道理的,看方法getFlyingSpeed()

我们想使用多态性并创建一个指向 a 的指针Bird

int main(int argc, _TCHAR* argv[])
{
    Animal *a = new Bird();
    std:: cout << a->getFlyingSpeed() << '\t'; // error!
}

我想,编译器不会在编译期间检查正确的分配,但只有两种类型的运行时检查。但我错了,因为...

错误 C2039: getFlyingSpeed: 不是Animal

有什么办法,如何使用并非所有类都通用的属性和多态性?还是只能强制运行时类型 chcek ?

也许这个模型不正确。如何重新设计它?

谢谢你的帮助。

4

7 回答 7

2

您的呼叫动物应仅提供所有动物(或您想在您的应用程序中使用的动物)共有的功能。对于并非所有动物共有的其他特征,可以使用多重继承来实现子类型动物的所需行为。所以一个子类型可能是 FlyingAnimals。

class FlyingAnimal
{
  public: 
    virtual short getFlyingSpeed() = 0;
    virtual ~FlyingAnimal(){}

};


class Bird : public Animal, public FlyingAnimal
{
    short flying_speed; 
  public:
    virtual short getFeet() { return 2; }  // always 2 feet
    virtual void setFeet(short feet_) { };
    virtual short getFlyingSpeed() { return flying_speed; }
};

您还可以为有脚的动物定义一个子类等。

或者,您可以像这样构建它,而不是多重继承:

class FlyingAnimal: public Animal
{
  public: 
    virtual short getFlyingSpeed() = 0;
    virtual ~FlyingAnimal(){}

};


class Bird : public FlyingAnimal
{
    short flying_speed; 
  public:
    virtual short getFeet() { return 2; }  // always 2 feet
    virtual void setFeet(short feet_) { };
    virtual short getFlyingSpeed() { return flying_speed; }
};

但是,在您的主要方法中,您需要定义您怀疑的动物类型。

int main(int argc, _TCHAR* argv[])
{
    FlyingAnimal *a = new Bird();
    std:: cout << a->getFlyingSpeed() << '\t'; // not an error anymore!
}
于 2012-08-13T19:39:57.717 回答
2
Animal *a = new Bird();

分配是正确的;Bird派生自Animal,因此 aBird*可以存储在 a 中Animal*。但是指针的类型是指向a的指针Animal,而不是指向的指针Bird,所以只能调用为 定义的成员函数Animal。这就是为什么编译器反对

a->getFlyingSpeed()

getFlyingSpeed不是 的成员函数Animal。另一方面,调用

a->getFeet()

没关系,因为getFeet是 的成员函数Animal。它将调用中getFeet定义的版本Bird,因为它是指向Bird的对象的类型。a

简而言之,C++ 是静态类型的;也就是说,类型是在编译时确定的,这就是为什么你只能Animala.

于 2012-08-13T19:43:56.687 回答
2

一个)

公开曝光setFeet()看起来很奇怪(好吧,可以说这是业余外科医生沙箱),但我建议重新设计对象层次结构,如下所示:

class Animal
{
public:
    virtual short getFeet() const = 0;
}

class SurgeonDelight : public Animal
{
    short feetNumber;
public:
    short getFeet() const {return feetNumber;}
    virtual void setFeet(short feet) {feetNumber = feet;}
}

然后,您从 继承所有具有恒定英尺数的动物,从 继承所有其他Animal动物SurgeonDelight。我们setFeet对其后代进行虚拟化,以便能够实现改变英尺数的一些副作用。SurgeonDelight隐藏实际数据成员以强制执行“更改的单一理由”。

二)

您可以使用 RTTI 和dynamic_cast,但基本上您应该构建BirdBird

有人告诉我“你的设计很糟糕,如果你必须做 dynamic_cast”。我倾向于同意。我编码超过 5 年,一次没有使用dynamic_cast(因为我们不支持它)。如果您的动物园中有更多具有飞行速度的动物(例如Turtle),您应该在您的层次结构中注入额外的泛化层。

我在少数库中看到的另一个不太好的方法是保留“扩展”API:

virtual void* extension(int functionId, int param) = 0;

是的,很丑,但可以让你到达那里

于 2012-08-13T19:44:42.163 回答
1

这是一个丰富的话题。考虑这个残酷的解决方案:

struct FlyingSpeed {
  bool meaningful;
  short speed;
  static const FlyingSpeed nofly; 
};

const FlyingSpeed FlyingSpeed::nofly = { false, 0 };

如果 fly_speed 方法返回一个 FlyingSpeed 类型的对象而不是一个短的。Animal 的每个实例都可以通过返回 FlyingSpeed 对象来响应 getFlyingSpeed 方法。鸟类、蝙蝠、蜜蜂会有有意义的 FlyingSpeed。蠕虫、狼、海象将具有无意义的 FlyingSpeed。袋鼠可能有飞行速度,也可能没有,这取决于你对跳跃的看法。蜘蛛在 balooning 时会有一个有意义的 FlyingSpeed,而一旦它们结网,就会有一个无意义的 FlyingSpeed。

类对象nofly是一种方便,因此陆生、树栖和水生动物可以具有简洁的构造函数。

于 2012-08-13T19:50:55.450 回答
1

这里有一些问题。

首先,在实例feet的生命周期内甚至可以更改属性是没有意义的。Animal即使人类有 2 英尺,他们也不能突然长出三分之一或 77 英尺。此外,对于同一动物类型的所有实例,它可能应该是相同的。所以feet应该每个类都固定,而不是每个实例都可以设置。

尝试这个:

class Animal
{
  private:
    short _feet;
  protected:
    Animal(short feet) : _feet(feet) { }
  public:
    short getFeet() { return _feet; }  // and call it 'getNumberOfFeet`; see below.
                                       // note: no longer virtual!
};

class Whale : public Animal
{
  public:
    Whale() : Animal(0) { }
};

class Mammal : public Animal { … };

class Ape : public Mammal
{
  public:
    Ape() : Animal(2) { }
};

(抱歉,如果有一些错误。我的 C++ 技能这些天有点生疏。)

另请注意,我将所有猿类的英尺数设置为 4。这是因为如果您HumanApe(假设我们都相信进化论......)派生出一个类,并且Ape有 4 英尺,这是否有意义通过说人类只有2个来反驳这一点?您的类型层次结构中会出现问题,因为Humans 显然不是正确Ape的 s。

顺便说一句,如果您的财产被称为numberOfFeet,而不是feet。使用feet,您会期望得到 astd::vector<Foot>或类似的东西。毕竟,当有人对你说“把你的脚给我”时,你会把腿放在他们的腿上什么的,但你不会说“两个!” ... 你会?


其次,对于您的代码示例:

int main(int argc, _TCHAR* argv[])
{
    Animal *a = new Bird();
    std:: cout << a->getFlyingSpeed() << '\t'; // error!
}

两种可能:

  • 你总是知道a指的是一个Bird. 那你为什么要声明变量aasAnimal*而不是 asBird*呢?

  • a可能指也可能不指Bird. 那你为什么打电话a->getFlyingSpeed()?如果a不是Bird实例会发生什么?

一种可能的解决方案:

class Animal
{
  public:
    void explainYourself() = 0;
};

class Bird : public Animal
{
    …
  public:
    void explainYourself()
    {
        std::cout << "I am flying at " << getFlyingSpeed() << " mph." << std::endl;
    }

};

class Mammal : public Animal
{
    …
  public:
    void explainYourself()
    {
        std::cout << "Hello, I have " << getFeet() << " feet." << std::endl;
    }
};


int main(…)
{
    Animal *a = new Bird();
    a->explainYourself();
    return 0;
}
于 2012-08-13T19:45:40.633 回答
0

A)我想所有动物一旦被创造出来就不能改变英尺的数量,除非你真的想允许这样做。因此,最好feet在 Animal 的构造函数中定义并移除关联的 setter。然后 Animal 的子类可以毫无问题地调用基本构造函数。我认为为有脚的动物开设一堂课是不值得的,因为表明动物根本没有脚通常是有意义的。

B) 建议最合适的是:为飞行动物创建一个接口类(使用纯虚拟方法)。调用它FlyingAnimal,您可以使用FlyingAnimal*指针来调用您的方法。如果您想知道动物是否可以在运行时飞行,bool canFly()可以使用一种方法,从而使动态演员表安全。

于 2012-08-13T19:44:46.607 回答
-1

您可以在运行时检查它是哪种类型dynamic_cast

void tryFlying(Animal *a) {
    Bird *b = dynamic_cast<Bird *>(a);
    if (b) {
        std::cout << "uninitialized speed junk " << b->getFlyingSpeed() << std::endl;
    } else {
        std::cout << "can't fly" << std::endl;
    }
}

int main() {
    Animal *a, *b;
    a = new Bird;
    b = new Mammal;
    tryFlying(a);
    tryFlying(b);
    return 0;
}

输出:

 uninitialized speed junk -27757
 can't fly

带有免费的内存泄漏(需要虚拟析构函数)。

于 2012-08-13T19:42:46.143 回答