虚函数在基类中是 const 而在派生类中不是 const

问题描述 投票:0回答:7

谁能解释一下以下代码的输出?

#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();
}

输出是

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

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

c++ inheritance virtual constants
7个回答
19
投票

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

这样做的结果是,在

printMessage
上调用
Animal
将仅使用
Animal
中的版本,而不管
Cow
中的版本(它不会覆盖它),而是从
Cow
调用它将使用
Cow
中的那个(因为它隐藏了
Animal
中的那个)。

要解决此问题,请删除

const
中的
Animal
,或添加
const
中的
Cow

在 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; 
    } 
};

注意在

override
之后添加了
printMessage()
。如果
printMessage
实际上没有覆盖基类版本,这将导致编译器发出错误。在这种情况下,您会收到错误。


7
投票

您有两种不同版本的

printMessage
,一种是
const
,一种不是。两人虽然同名,却毫无血缘关系。
Cow
中的新函数隐藏了
Animal
中的函数,因此当您直接调用它时,编译器仅考虑
Cow
版本。


1
投票

刚刚尝试过。将

const
添加到 Cow 类中,它将起作用。

virtual void printMessage () const
两个班级。


1
投票

我花了一段时间才理解Peter Alexander的hiding答案,但另一种理解方式如下:

假设您在 Cow 类中拼写错误了方法名称,但在 Animal 类中拼写正确:

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

然后当你有

Animal *animal;

你只能打电话

animal->printMessage();

但你不能打电话

animal->mispelledPrintMessage();

因为 Animal 类中不存在拼写错误的PrintMessage()。它是 Cow 类中的一个全新方法,因此不能通过基指针进行多态调用。

因此,让 Animal 方法在签名中包含 const,但在 Cow 方法中不包含 const,这有点类似于派生类中稍微拼写错误的方法名称。

PS:另一个第四个解决方案(1 将两个方法设为 const,2 将两个方法设为非 const,或 3 使用新的 2011 override 关键字),是使用强制转换,将 Animal 指针强制转换为 Cow 指针:

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

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

PS:出于这个原因,我总是尝试在基类和所有派生类的签名中使用 const 编写 toString() 方法。 另外,在 toString() 签名中包含 const 允许您使用 const 非 const 对象调用 toString() 。 如果您省略了 const,并尝试使用 const 对象传递调用 toString(),GCC 编译器会抱怨 discards qualifiers 错误消息:

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*,您来自 Animal 类的 printMessage 将不可用。 因此 ((Cow*)animal)->printMessage() 需要是 ((Cow*)animal)->mispelledPrintMessage()


0
投票

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


0
投票

避免此类问题的一个好方法是始终使用

override
final
如果您相信您正在编码的子类方法将替换基类方法的实现(或者提供第一个方法,如果基类方法类方法是纯虚拟的...)。 此最佳实践规则也是工业编码标准(如 Autosar C++)的一部分,您可以在其中找到完善的规则,如

规则 A10-3-1(必需、实施、自动化): 虚函数声明应包含三个说明符之一:(1) virtual,(2) override,(3) Final。

如果您碰巧在声明的重写方法中犯了拼写错误(或不匹配

const
说明符,或者只是另一个打字错误),那么编译器将立即拒绝您的程序并让您思考拼写错误在哪里。
相反的情况(你想要隐藏现有的 

override

方法而不覆盖它)的情况要少得多。此外,这种设计被认为是非常糟糕的做法,因为每个人都会混淆具有相同/相似名称的两种方法。

    

© www.soinside.com 2019 - 2024. All rights reserved.