2011-10-12 21 views
1

我有这样的代码:虚拟关键字使得人迹罕至的方法从派生的对象

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

+0

使用上面的static_cast <>是未定义的行为。该代码已损坏,仅适用于编译器及其当前标志的奇想。 –

+0

代码会导致未定义的行为,除非指向的对象实际上是一个“Special#”实例,否则不能将'Super *'强制转换为'Special#*'对象,而在您的情况下则不是。 –

回答

2

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

将一个指针类型投射到另一个指针类型不会更改指向的东西的类型。它怎么可能?虚函数的全部要点是能够调用“通用”指针的方法并获得正确的派生类方法。

为什么使用虚拟功能的典型例子是音乐家。通过调用Play方法,每个Musician *它可以通过一个函数,导致整个管弦乐队播放。对于Pianist,必须调用Pianist::Play

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

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

事实上,你声称'正确'的结果是灾难性的。运行Special1::showName()this指针指向的对象不是类型Special1(或从中派生的东西)是未定义的行为,并且很容易导致崩溃。

+2

由于程序具有未定义的行为,您无法真正说出任何一个输出是“正确的”。 –

+0

@Mike Seymour:你是对的。您不能通过指向类成员不是实例的类型的指针访问类成员。 (我把这一点加到我的答案上,谢谢。) –

+0

我真的不喜欢你使用上面的'正确'这个词。未定义的行为没有正确的行为。 –

2

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

你的静态投打破了所有的规则,是未定义行为(任何事情都有可能发生),因为你告诉编译器o1Special1,而事实上,它是不是一种类型的Special1。当创建了一堆衍生对象,并将它们存储在一个指针/容器保持指针指向对象

virtual函数通常是有用的。而不是相反。您想要创建类型为Special1Special2的指针并将它们存储在指向super的指针中。

2

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

1

您的代码只是未定义。
试图说明为什么它以特定的方式工作是没用的。

这里:

Super *oSpec=new Super; 

type中超的目标oSpec点。

因此在这些陈述中。演员阵容有未定义的行为,因为oSpec指向的对象既不是特殊1也不是特殊2)。

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

如果使用dynamica_cast <>(像你应该铸造起来的类层次结构时)。你会发现结果是NULL。

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

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

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