4

Visual c++ 是否为只有一个实现的纯类去虚拟化函数?例如:

class ICar
{
public:
virtual void Break() = 0;
};

class CarImpl : public ICar
{
public:
virtual void Break(){ .... }
};
4

2 回答 2

6

OP 问题自然分为 3 个问题:

  1. VC++ 2010 是否进行了所描述的去虚拟化 - 答案是:
  2. 它可以做到吗?答案是:的,理论上它可以在某些情况下做到。
  3. 为什么 VC++ 不这样做。- 我们只能推测...

以下是详细信息:

1.这样的优化是VC++做的吗

为了证明这种优化没有完成,我们需要在 Project Properties/Configuration Properties/C/C++/Output Files 中启用汇编语言列表:将 Assembler Output 设置为“Assembly With Source Code”(/FAs)”。

这是来自 OP 的稍微修改过的 C++ 代码(我将 ICar 从抽象类更改为普通类,它不会改变问题的要点):

#include "stdafx.h"

class ICar
{
public:
    virtual void Accelerate(){printf("%s", "a\n");};
    virtual void Break(){printf("%s", "b\n");};
};

class CarImpl : public ICar
{
public:
    virtual void Accelerate(){ printf("%s", "accelerate\n"); }
    virtual void Break(){ printf("%s", "break\n"); }
    void Fly() { printf("%s", "fly\n"); }
};

int _tmain(int argc, _TCHAR* argv[])
{
    ICar *pCar = new CarImpl();
    pCar->Break();

    CarImpl *pCarImpl = new CarImpl();
    pCarImpl->Fly();

    CarImpl carImpl;
    carImpl.Break();
    carImpl.Fly();

    return 0;
}

首先,(注 1)让我们注意carImpl.Break();不使用虚函数。这不是优化的结果——它是 C++ 的一个特性:如果在编译期间知道对象的类型,则不使用虚函数机制。虚函数机制仅在涉及指针或引用时使用。

其次,让我们启用优化 /O2 并查看为pCar->Break();(虚拟方法)和pCarImpl->Fly();(非虚拟方法)生成的汇编程序。

对于 Break() 的调用,我们将看到:

; 24   :     pCar->Break();

    mov edx, DWORD PTR [eax]
    mov ecx, eax
    mov eax, DWORD PTR [edx+4]
    call    eax

EAX 包含一个指向 CarImpl 对象的指针(从前面未显示的汇编程序行可以清楚地看出)。在第一条 mov 指令中,CarImpl 对象的第一个 dword 被加载到 EDX 中(对象的第一个 dword 通常是 vtbl 的地址),然后thisCarImpl 的第一个 dword 被加载到 ECX 中(这对我们来说并不重要),然后 dword 与将 EDX 指向的点(虚函数表中的第二个函数)的偏移量 4 加载到 EAX 中,然后调用完成。

在 Fly() 的情况下,我们将看到:

; 27   :     pCarImpl->Fly();

    push    OFFSET ??_C@_04PPJAHJOB@fly?6?$AA@
    push    OFFSET ??_C@_02DKCKIIND@?$CFs?$AA@
    call    _printf

这只是 printf 的内联,其中传递了两个参数。

因此,很明显,在 Break() 的情况下,vtable 的使用并未优化。

2.能不能做这样的优化

原则上它可以被优化。我在 M.Ellis, B. Stroustrup, Addison-Wesley 1990 的“The Annotated C++ Reference Manual”中找到了以下语句:第 10.2 章(我有这本书的翻译,我正在翻译回英文 :-) 所以它可能不是 Stroustroup 的确切措辞。)

当在编译时知道对象的确切类型时,就不需要虚函数机制。相反,实现可以生成类成员函数的普通调用。(DK:我们代码中 carImpl.Break() 的情况,请参阅我的 NOTE 1) ... 当通过指针或引用调用虚函数时,对象的实际类型可能不是静态已知的,因此 virtual 的机制应该使用函数。对控制流有足够了解的编译器可以放弃对虚函数的调用,即使在某些情况下,例如通过以下代码中的 bp 调用:

struct base {
    virtual void vf1();
}
class derived : public base{
public:
    void vf1();
}

void g()
{
    derived d;
    base* bp = &d;
    bp->vf1();
}

...内联虚函数完全有意义并且经常使用。自然,内联仅用于将内联函数应用于已知类型的对象的地方。(DK:我认为这里 B. Stroustrup 也参考了我们的 carImpl.Break() 案例;即 NOTE1 中描述的案例)。

3. 为什么没有完成

虽然这在 OP 中并没有字面上问过,但也许这是一个隐藏的问题。我同意 Alex Cohn 的评论之一(说得好):

它可以,但它没有。可能这种情况不会经常发生,不足以证明可靠地优化此类调用所需的资源是合理的。

于 2012-10-20T23:07:09.413 回答
3

编辑:这个答案被我的第二个答案 2012-10-20 淘汰了。我没有删除它以保留评论。

VC++ 不可能,因为其他派生类可以链接到已经编译的 dll 或 exe 模块。

于 2012-10-18T20:09:15.130 回答