为什么在基类指针转换时使用虚函数会产生相同的结果?

时间:2014-03-16 13:03:04

标签: c++ inheritance virtual-functions

我正在研究虚函数和指针。下面的代码让我思考,当我们可以按照我们想要的方式键入转换基类指针时,为什么需要虚函数?

class baseclass {
public:
    void show() {
        cout << "In Base\n";
    }
};

class derivedclass1 : public baseclass {
public:
    void show() {
        cout << "In Derived 1\n";
    }
};

class derivedclass2 : public baseclass {
public:
    void show() {
        cout << "In Derived 2\n";
    }
};

int main(void) {
    baseclass * bptr[2];
    bptr[0] = new derivedclass1;
    bptr[1] = new derivedclass2;

    ((derivedclass1*) bptr)->show();
    ((derivedclass2*) bptr)->show();

    delete bptr[0];
    delete bptr[1];

    return 0;
}

如果我们在基类中使用virtual,则会得到相同的结果。

In Derived 1
In Derived 2

我错过了什么吗?

3 个答案:

答案 0 :(得分:2)

如果使用虚函数,则调用函数的代码不需要知道对象的实际类。你只需要盲目调用函数,就会执行正确的函数。这是多态性的基础。

类型转换总是有风险的,并且可能导致大型程序中的运行时错误。

您的代码应为open for extension but closed for modifications

希望这有帮助。

答案 1 :(得分:2)

您的示例似乎有效,因为没有数据,没有虚方法,也没有多重继承。尝试将int value;添加到derivedclass1,将const char *cstr;添加到derivedclass2,在相应的构造函数中初始化这些内容,然后将这些内容添加到相应的show()方法中。

您将看到show()将如何打印垃圾值(如果您将指针转换为derivedclass1则不显示)或崩溃(如果您实际上将类指针转移到derivedclass2不属于那种类型),或表现得很奇怪。


C ++类成员函数AKA方法只不过是函数,它们带有一个隐藏的额外参数this指针,并且它们假定它指向一个正确类型的对象。所以当你有一个derivedclass1类型的对象,但是你把它指向它时要输入derivedclass2,那么没有虚方法会发生这样的事情:

  • derivedclass2的方法被调用,因为你明确地说“这是指向derivedclass2”的指针。
  • 该方法获取指向实际对象this的指针。它认为它指向derivedclass2的实际实例,它在某些偏移处会有某些数据成员。
  • 如果对象实际derivedclass1,则该内存包含完全不同的内容。因此,如果方法认为有一个字符指针,但事实上没有,那么访问它指向的数据可能会访问非法地址并崩溃。

如果您改为使用虚方法,并且具有指向公共基类的指针,那么当您调用方法时,编译器会生成用于调用正确方法的代码。它实际上插入代码和数据(使用填充了虚拟方法指针的表,通常称为 vtable ,每个类一个,指向它的指针,每个对象/实例一个),它知道调用右边的方法。因此,当您调用虚方法时,它不是直接调用,而是该对象具有指向真实类的 vtable 的额外指针,它指示应该使用哪种方法被称为该对象。


总之,类型转换绝不是虚拟方法的替代方法。并且,作为旁注,每个类型的演员阵容都是一个问题“为什么会在这里演出?这个软件是否存在根本问题,如果它需要演员?”。类型转换的合法用例确实非常罕见,特别是对于OOP对象。此外,永远不要使用带有对象指针的C风格类型转换,如果你真的需要施放,请使用static_castdynamic_cast

答案 2 :(得分:1)

您需要虚拟功能,以便在运行时之前不知道派生类型(例如,当它取决于用户输入时)。

在您的示例中,您对derivedclass2derivedclass1进行了硬编码转换。那你在这做什么?

void f(baseclass * bptr)
{
    // call the right show() function
}

也许您的困惑源于您尚未遇到虚拟功能实际有用的情况。当你总是在编译时准确地知道你正在操作的具体类型时,你就根本不需要虚函数。

示例代码中的其他两个问题:

  1. 使用C风格的强制转换而不是C ++风格的dynamic_cast(当然,当你使用虚拟函数解决它们旨在解决的问题时,你通常不需要进行投射)。 / LI>
  2. 以多态方式处理数组。请参阅Scott Meyer的更有效的C ++ 一书中的第3项(&#34;从不以多态方式处理数组&#34;)。