没有虚函数的多态性

时间:2018-01-31 20:32:29

标签: c++ inheritance optimization virtual-functions

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

所以我需要一个标识我正在处理的类的类型的变量,所以我可以使用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语句 - >生成更有效吗?然后使用派生类特定函数。

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

2 个答案:

答案 0 :(得分:2)

  

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

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

  

问题2:将switch语句放在函数中以决定它应该根据类型值或switch语句 - >生成更有效吗?然后使用派生类特定函数。

肯定没有!这是糟糕(sic!)类继承层次结构设计的重要指标。

  

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

在不使用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>保证安全保证绑定也会导致编译错误。

注意(几乎免责声明):

请勿将该图案用作金锤!它也有它的缺点:

  • 提供抽象接口更加困难,如果没有先前的类型特征检查或概念,您将会使您的客户难以阅读模板实例化时的编译器错误消息。
  • 这不适用于插件,就像架构模型一样,你真的想要后期绑定,并在运行时加载模块。

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

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

答案 1 :(得分:0)

继续使用虚函数,但要确保每个函数都做了足够的工作,间接调用的开销是微不足道的。这应该不是很难做到,虚拟调用非常快 - 如果不是,它就不会成为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;
}

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