虚拟关键字使派生对象无法访问方法

时间:2011-10-12 17:03:35

标签: c++ inheritance virtual

我有这段代码:

#include <iostream>

class Super{
public:
    virtual void showName();
};

class Special1 : public Super {
public:
    void showName();
    void sayHello();
};

class Special2 : public Super {
public:
    void showName();
    void sayGoodbye();
};

void Super::showName() {
    std::cout << "I'm super!" << std::endl;
}

void Special1::showName() {
    std::cout << "I'm special1" << std::endl;
}

void Special1::sayHello() {
    std::cout << "Hello" << std::endl;
}

void Special2::showName() {
    std::cout << "I'm special2" << std::endl;
}

void Special2::sayGoodbye() {
    std::cout << "Goodbye" << std::endl;
}

int main () {
    Super *oSpec=new Super;

    Special1 *o1=static_cast<Special1 *>(oSpec);
    Special2 *o2=static_cast<Special2 *>(oSpec);

    oSpec->showName();
    o1->showName();
    o2->showName();

    o1->sayHello();
    o2->sayGoodbye();

    delete oSpec;

    return 0;
}

当我运行它时,它会显示以下输出:

I'm super!
I'm super!
I'm super!
Hello
Goodbye

但是,如果我从virtual类的声明中删除Super关键字:

class Super{
public:
    /*virtual*/ void showName();
};

输出变为正确的输出:

I'm super!
I'm special1
I'm special2
Hello
Goodbye

然后,我的问题是,为什么virtual关键字的存在使指针o1o2运行方法Super::showName()而不是Special1::showName()Special2::showName()

4 个答案:

答案 0 :(得分:2)

输出更接近于virtual更正并且不正确(除非你真的想要)。强制转换不会改变对象的类型,只会改变指针的类型。它们仍然是Super类型,因此应该运行Super::showName

将一个指针类型转换为另一个指针类型不会更改指向的事物的类型。怎么可能呢?虚函数的重点是能够在'泛型'指针上调用方法并获得正确的派生类方法。

您使用虚拟功能的典型例子是音乐家。您可能有一个函数可以通过在传递的每个Play上调用Musician *方法来使整个管弦乐队演奏。对于Pianist,必须致电Pianist::Play

通常,编译器会确定在编译时调用哪个函数 - 早期绑定。编译器必须知道的唯一信息是指针的类型。 virtual关键字导致绑定在运行时更晚发生,此时类成员的实际类型已知。

顺便说一下,您仍然可以使用范围覆盖来调用基类方法。例如o1->Super::showName();

事实上,你声称“正确”的结果是灾难性的。使用Special1::showName()指针指向不属于this类型的对象(或从其派生的内容)运行Special1是未定义的行为,很容易导致崩溃。

答案 1 :(得分:2)

因为main中的代码错误。对象都说“我超级”,因为对于他们(实际上,在背景中),大多数派生类型仍然是super。这就是他们被创造出来的。

你的静态强制转换破坏了所有规则,是未定义的行为(任何事情都可能发生),因为你告诉编译器o1Special1,实际上它是不是一种Special1

当您创建一堆派生的对象时,

virtual函数通常很有用,并将它们存储在一个指针/容器中,该指针/容器包含指向 base 对象的指针。不是相反。您希望创建Special1Special2类型的指针,并将它们存储在指向super的指针中。

答案 2 :(得分:2)

你的演员表({{​​1}})在两种情况下都是未定义的行为,因此就语言而言,任何输出都是可以接受的。你已经使用了Special1 *o1=static_cast<Special1 *>(oSpec);类型的对象并且对编译器撒了谎,告诉它它确实是一个派生类。在这种情况下,会发生的情况是您仍然得到父类Super的结果。

答案 3 :(得分:1)

您的代码尚未定义。
试图解释为什么它以特定的方式工作是没用的。

下面:

Super *oSpec=new Super;

oSpec指向type Super。

的对象

因此在这些陈述中。强制转换具有未定义的行为,因为oSpec指向的对象既不是Special 1也不是Special 2。

Special1 *o1=static_cast<Special1 *>(oSpec);
Special2 *o2=static_cast<Special2 *>(oSpec);

如果您使用dynamica_cast&lt;&gt; (就像你在编译类层次结构时一样)。你会发现结果是NULL。

Special1 *o1=dynamic_cast<Special1 *>(oSpec);
Special2 *o2=dynamic_cast<Special2 *>(oSpec);

std::cout << "O1(" << (void*)o1 << ") O2(" << (void*)o2 << ")\n";

这将显示两个指针都是NULL。