2011-09-21 43 views
8

任何人都可以解释下面代码的输出吗?虚拟函数在基类中是常量而在派生中不是常量

#include <iostream> 
#include <string> 
class Animal 
{ 
public: 
    Animal(const std::string & name) : _name(name) { } 
    ~Animal() { } 
    virtual void printMessage() const 
    { 
     std::cout << "Hello, I'm " << _name << std::endl; 
    } 

private: 
    std::string _name; 
    // other operators and stuff 
}; 

class Cow : public Animal 
{ 
public: 
    Cow(const std::string & name) : Animal(name) { } 
    ~Cow() { } 
    virtual void printMessage() 
    { 
     Animal::printMessage(); 
     std::cout << "and moo " << std::endl; 
    } 
}; 

int main() { 
    Cow cow("bill"); 
    Animal * animal = &cow; 
    cow.printMessage(); 
    animal->printMessage(); 
} 

输出是

你好,我是比尔
和哞
你好,我是比尔

我不明白为什么。指针动物指向Cow类型的对象。 printMessage是一个虚函数。为什么牛类的实现不是被调用的呢?

+0

+1为完整的最小工作示例 – Flexo

+0

您是否尝试将Cow类中的函数更改为virtual void printMessage()const? –

回答

12

Cow不会覆盖虚拟函数Animal,因为它具有不同的签名。实际发生的是Cow隐藏函数Animal

这样做的结果是,在一个Animal调用printMessage将只使用版本Animal,无论一个在Cow(它不覆盖它),但是从Cow调用它会使用一个在Cow(因为它隐藏了一个从Animal)。

要解决此问题,请删除Animal中的const,或在Cow中添加const

在C++ 2011,您将能够使用override关键字来避免这样的微妙陷阱:

class Cow : public Animal 
{ 
public: 
    Cow(const std::string & name) : Animal(name) { } 
    ~Cow() { } 
    virtual void printMessage() override 
    { 
     Animal::printMessage(); 
     std::cout << "and moo " << std::endl; 
    } 
}; 

通知printMessage()后加入override。如果printMessage没有实际覆盖基类版本,这将导致编译器发出错误。在这种情况下,你会得到错误。

+2

如果基类函数必须是const并且派生的函数必须是非const的,那么可以使用非虚函数接口语法,其中基函数不是虚拟的,但是调用一个非常量的私有虚拟接口语言,因此可以被派生类中的非const函数覆盖。 – derpface

+0

谢谢derpface,正是我需要:) –

6

您有两个不同版本的printMessage,其中一个是const,另一个不是。尽管它们有相同的名字,但它们是无关的。 Cow中的新函数隐藏了Animal中的函数,因此,当您直接调用编译器时,编译器仅考虑Cow版本。

0

刚刚尝试过。将const添加到牛类,它将工作。

virtual void printMessage() const这两个类。

1

我花了一段时间来了解彼得·亚历山大的隐藏答案,但另一种方式来了解其计算方法如下:

说你拼写错误的牛类中的方法名,但拼写是否正确动物类:

Animal::printMessage() 
Cow::mispelledPrintMessage() 

然后当你有一个

Animal *animal; 

,你只能叫

animal->printMessage(); 

,但你不能叫

animal->mispelledPrintMessage(); 

因为mispelledPrintMessage()不会在动物类存在。它的一个全新的方法在牛类中,所以它不能被多态调用通过一个基指针。

所以拥有Animal方法在签名中有const,但在Cow方法中不是类似于派生类中稍微拼错的方法名称。 PS:另一个第四种解决方案(1使两个方法都是const,2使两个方法都是非常量,或者使用新的2011覆盖关键字),就是使用一个强制类型来强制Animal指针变为牛指针:

((Cow*)animal)->printMessage(); 

但这是一个非常丑陋的HACK,我不会推荐它。

PS:我总是试着写我的toString()在基类中的签名和所有派生类因为这个原因与常量的方法。另外,如果在toString()签名中包含const,则可以使用const const non-const对象调用toString()。假如你不是离开了常量,并试图通过一个const对象调用toString(),GCC编译器将与丢弃预选赛抱怨错误消息:

const Bad' as `this' argument of `std::string Bad::toString()' discards qualifiers 

此代码:

#include <iostream> 
#include <string> 

class Bad 
{ 
public: 
     std::string toString() 
     { 
       return ""; 
     } 
}; 

int main() 
{ 
     const Bad  bad; 
     std::cout << bad.toString() << "\n"; 
     return 0; 
} 

因此,最后,由于Cow不会更改任何数据成员,所以最好的解决方案可能是在派生的Cow类中为printMessage()添加const,以便BASE Animal和DERIVED Cow类在其签名中具有const 。

丹尼斯比德纳 -ahd 310

1

更正丹尼斯的职务。

请注意,您声明您可以将动物(这是一只动物*)施放到牛*的解决方案。但是,一旦Cow *您的动物类的printMessage将不可用。因此((Cow *)animal) - > printMessage()需要是((Cow *)animal) - > mispelledPrintMessage()

0

虚拟子类 - >不需要。您只需在子类中添加const以使其具有相同的签名

相关问题