简体   繁体   English

没有虚函数的多态

[英]Polymorphism Without Virtual Functions

I am trying to optimize the run time of my code and I was told that removing unnecessary virtual functions was the way to go. 我正在尝试优化代码的运行时间,并被告知删除不必要的虚函数是解决之道。 With that in mind I would still like to use inheritance to avoid unnecessary code bloat. 考虑到这一点,我仍然想使用继承来避免不必要的代码膨胀。 I thought that if I simply redefined the functions I wanted and initialized different variable values I could get by with just downcasting to my derived class whenever I needed derived class specific behavior. 我以为只要我重新定义想要的函数并初始化不同的变量值,只要我需要派生类特定的行为,就可以将其向下转换到派生类。

So I need a variable that identifies the type of class that I am dealing with so I can use a switch statement to downcast properly. 因此,我需要一个变量来标识要处理的类的类型,以便可以使用switch语句正确地向下转换。 I am using the following code to test this approach: 我正在使用以下代码来测试这种方法:

Classes.h 类.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 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;
}

I get the output expected: 0 1 我得到预期的输出:0 1

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)? 问题1:是否有更好的类型存储方式,这样我就不必为创建的对象的每个实例浪费内存(就像static关键字允许的那样)?

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. 问题2:将switch语句放入函数中以根据类型值决定应执行的操作是否更有效,或者将switch语句-> downcast然后使用派生的类特定函数,会更有效。

Question 3: Is there a better way to handle this that I am entirely overlooking that does not use virtual functions? 问题3:是否有更好的方法来处理我完全忽略的不使用虚函数的方法? For Example, should I just create an entirely new class that has many of the same variables 例如,我是否应该创建一个具有许多相同变量的全新类

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)? 问题1:是否有更好的类型存储方式,这样我就不必为创建的对象的每个实例浪费内存(就像static关键字允许的那样)?

There's the typeid() already enabled with RTTI, there's no need you implement that yourself in an error prone and unreliable way. RTTI已经启用了typeid() ,您无需以容易出错和不可靠的方式实现自己。

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. 问题2:将switch语句放入函数中以根据类型值决定应执行的操作是否更有效,或者将switch语句-> downcast然后使用派生的类特定函数,会更有效。

Certainly no! 当然没有! That's a heavy indicator of bad (sic!) class inheritance hierarchy design. 这是糟糕的类继承层次结构设计的重要指标。

Question 3: Is there a better way to handle this that I am entirely overlooking that does not use virtual functions? 问题3:是否有更好的方法来处理我完全忽略的不使用虚函数的方法? For Example, should I just create an entirely new class that has many of the same variables 例如,我是否应该创建一个具有许多相同变量的全新类

The typical way to realize polymorphism without usage of virtual functions is the CRTP (aka Static Polymorphism ). 在不使用virtual函数的情况下实现多态的典型方法是CRTP (又名静态多态 )。
That's a widely used technique to avoid the overhead of virtual function tables when you don't really need them, and just want to adapt your specific needs (eg with small targets, where low memory overhead is crucial). 这是一种广泛使用的技术,可以在您实际上不需要虚拟函数表而只是想适应您的特定需求时(例如,对于小型目标,而低内存开销至关重要)来避免虚拟函数表的开销。

Given your example 1 , that would be something like this: 给定您的示例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; }
};

All binding will be done at compile time, and there's zero runtime overhead. 所有绑定都将在编译时完成,并且运行时开销为零。
Also binding is safely guaranteed using the static_cast , that will throw compiler errors, if B doesn't actually inherits A<B> . 如果B实际上没有继承A<B> ,则还可以使用static_cast 安全地保证绑定,这将引发编译器错误。


Note (almost disclaimer): 注意(几乎免责声明):

Don't use that pattern as a golden hammer ! 不要把那个图案当作金锤 It has it's drawbacks also: 它也有缺点:

  • It's harder to provide abstract interfaces, and without prior type trait checks or concepts , you'll confuse your clients with hard to read compiler error messages at template instantiantion. 提供抽象接口比较困难,而且如果没有事先的类型特征检查或概念 ,模板实例化时,您的客户端就会难以阅读编译器错误消息,从而使客户感到困惑。
  • That's not applicable for plugin like architecture models, where you really want to have late binding , and modules loaded at runtime. 这不适用于架构模型之类的插件 ,在该插件中您确实要延迟绑定 ,并在运行时加载模块。

  • If you don't have really heavy restrictions regarding executable's code size and performance, it's not worth doing the extra work necessary. 如果您对可执行文件的代码大小和性能没有真正严格的限制,则不值得进行必要的额外工作。 For most systems you can simply neglect the dispatch overhead done with virtual function defintions. 对于大多数系统,您可以简单地忽略使用virtual函数定义完成的调度开销。


1) The semantics of GetType() isn't necessarily the best one, but well ... 1) GetType() 的语义不一定是最好的,但是...

Go ahead and use virtual functions, but make sure each of those functions is doing enough work that the overhead of an indirect call is insignificant. 继续使用虚拟函数,但要确保每个函数都能完成足够的工作,以至于间接调用的开销微不足道。 That shouldn't be very hard to do, a virtual call is pretty fast - it wouldn't be part of C++ if it wasn't. 这应该不是很难做到的,虚拟调用非常快-如果不是的话,它就不会成为C ++的一部分。

Doing your own pointer casting is likely to be even slower, unless you can use that pointer a significant number of times. 除非您可以大量使用该指针,否则执行自己的指针转换可能会更慢。

To make this a little more concrete, here's some code: 为了更具体一点,下面是一些代码:

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;
}

The code that switches using a type code is not only much harder to read, it's probably slower as well. 使用类型代码切换的代码不仅不易阅读,而且速度可能也较慢。 Doing more work inside a virtual function ends up being both cleanest and fastest. 在虚拟函数中做更多的工作最终既干净又最快。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM