没有虚函数的多态性

Polymorphism Without Virtual Functions

我正在尝试优化代码的 运行 时间,有人告诉我删除不必要的虚函数是可行的方法。考虑到这一点,我仍然想使用继承来避免不必要的代码膨胀。我认为,如果我简单地重新定义我想要的函数并初始化不同的变量值,只要我需要派生的 class 特定行为,我就可以通过向下转换到派生的 class 来获得。

所以我需要一个变量来标识我正在处理的 class 的类型,这样我就可以使用 switch 语句来正确地进行向下转型。我正在使用以下代码来测试这种方法:

Classes.h

#pragma once

class A {
public:
    int type;
    static const int GetType() { return 0; }
    A() : type(0) {}
};

class B : public A {
public:
    int type;
    static const int GetType() { return 1; }
    B() : {type = 1}
};

Main.cpp

#include "Classes.h"
#include <iostream>
using std::cout;
using std::endl;
using std::getchar;

int main() {
    A *a = new B();
    cout << a->GetType() << endl;
    cout << a->type;
    getchar();
    return 0;
}

我得到预期的输出:0 1

问题 1:是否有更好的存储类型的方法,这样我就不需要为创建的每个对象实例浪费内存(就像 static 关键字允许的那样)?

问题 2:将 switch 语句放在函数中以根据类型值决定它应该做什么更有效,还是 switch 语句 -> 向下转换然后使用派生的 class 特定函数.

问题3:有没有更好的方法来处理这个我完全忽略的不使用虚函数的方法?例如,我是否应该创建一个具有许多相同变量的全新 class

继续使用虚函数,但要确保这些函数中的每一个都在做足够的工作,这样间接调用的开销就可以忽略不计了。这不应该很难做到,虚拟调用非常快——如果不是,它就不会成为 C++ 的一部分。

进行自己的指针转换可能会更慢,除非您可以多次使用该指针。

为了更具体一点,这里有一些代码:

class A {
public:
    int type;
    int buffer[1000000];
    A() : type(0) {}
    virtual void VirtualIncrease(int n) { buffer[n] += 1; }
    void NonVirtualIncrease(int n) { buffer[n] += 1; }
    virtual void IncreaseAll() { for i=0; i<1000000; ++i) buffer[i] += 1; }
};

class B : public A {
public:
    B() : {type = 1}
    virtual void VirtualIncrease(int n) { buffer[n] += 2; }
    void NonVirtualIncrease(int n) { buffer[n] += 2; }
    virtual void IncreaseAll() { for i=0; i<1000000; ++i) buffer[i] += 2; }
};

int main() {
    A *a = new B();

    // easy way with virtual
    for (int i = 0; i < 1000000; ++i)
        a->VirtualIncrease(i);

    // hard way with switch
    for (int i = 0; i < 1000000; ++i) {
        switch(a->type) {
            case 0:
                a->NonVirtualIncrease(i);
                break;
            case 1:
                static_cast<B*>(a)->NonVirtualIncrease(i);
                break;
        }
    }

    // fast way
    a->IncreaseAll();

    getchar();
    return 0;
}

使用类型代码进行切换的代码不仅更难阅读,而且可能更慢。在虚函数中做更多的工作最终变得最干净和最快。

Question 1: Is there a better way to store type so that I do not need to waste memory for each instance of the object created (like the static keyword would allow)?

typeid() 已经启用了 RTTI,您无需以容易出错且不可靠的方式自行实施。

Question 2: Would it be more effective to put the switch statement in the function to decide that it should do based on the type value, or switch statement -> downcast then use a derived class specific function.

当然不是!这是 糟糕 (原文如此!)class 继承层次结构设计的重要指标。

Question 3: Is there a better way to handle this that I am entirely overlooking that does not use virtual functions? For Example, should I just create an entirely new class that has many of the same variables

不使用 virtual 函数实现多态性的典型方法是 CRTP(又名 静态多态性)。
这是一种广泛使用的技术,可以避免 virtual function tables 的开销,当你并不真正需要它们时,只是想适应你的特定需求(例如,对于小目标,低内存开销是至关重要的)。

给定你的例子1,那将是这样的:

template<class Derived>
class A {
protected:
    int InternalGetType() { return 0; }
public:
    int GetType() { static_cast<Derived*>(this)->InternalGetType(); }
};

class B : public A<B> {
    friend class A<B>;
protected:
    int InternalGetType() { return 1; }
};

所有绑定都将在编译时完成,运行时开销为零。
此外,使用 static_cast 保证绑定 安全 ,如果 B 实际上没有继承 A<B>.[=23=,这将引发编译器错误]


注(几乎免责声明):

不要将该图案用作金锤子!它也有缺点:

  • 很难提供抽象接口,而且没有事先类型特征检查或概念,你会在模板实例化时用难以阅读的编译器错误消息混淆你的客户。

  • 这不适用于 插件 之类的体系结构模型,您真正想要 后期绑定 和模块在运行时加载。

  • 如果您对可执行文件的代码大小和性能没有真正严格的限制,那么不值得做必要的额外工作。对于大多数系统,您可以简单地忽略使用 virtual 函数定义完成的调度开销。


1)GetType()的语义不一定是最好的,但是...