C++ 虚拟函数调用的优化
我有一个关于虚拟函数调用优化的问题。我在某个地方读到过(问题是我现在找不到这篇文章),通过使用类似以下的构造,可以优化v表查找:C++ 虚拟函数调用的优化,c++,optimization,virtual-functions,vtable,C++,Optimization,Virtual Functions,Vtable,我有一个关于虚拟函数调用优化的问题。我在某个地方读到过(问题是我现在找不到这篇文章),通过使用类似以下的构造,可以优化v表查找: // Base.h class Base { public: virtual void Foo() = 0; }; // Concrete.h class Concrete : public Base { public: virtual void Foo() { // do something; } }; //Som
// Base.h
class Base
{
public:
virtual void Foo() = 0;
};
// Concrete.h
class Concrete : public Base
{
public:
virtual void Foo()
{
// do something;
}
};
//Some.h
extern Base* const g_object;
// Some.cpp
Concrete on_stack_concrete;
Base* const g_object = &on_stack_concrete;
这个技巧应该是使用一个常量指针指向一个在堆栈上指定的变量(而不是dynamicali),编译器肯定会对此进行优化。因此,每当用户调用g_object->Foo()时,//do something部分将被执行,而无需v表查找
这是真的还是假的
提前感谢您的重播
编辑:
这种构造的可能用途是限制具体实现的接口。当然,有人会认为“受限”方法应该是私有的,但有时库的其他模块需要访问对象的这些公共附加方法,而不允许用户操作这些方法。因此,例如,使用#defines,可以创建类似以下内容的代码:
// Some.cpp
#ifdef _WIN32
Win32Concrete concrete;
#elif defined _UNIX
UnixConcrete concrete;
#endif
Base* const g_global = &concrete;
事实上,这些类的声明只能在CPP文件中定义,因此,用户不知道它们的存在
问题不是为什么首先要使用这样的常量指针,而是在这种情况下是否可以优化v表查找。您似乎误用了
虚拟
virtual
实现运行时多态性。而您描述的场景并不使用或需要它。任何编译环境中都不可能同时存在Win32Concrete
和UnixConcrete
而不是:
// Some.cpp
#ifdef _WIN32
Win32Concrete concrete;
#elif defined _UNIX
UnixConcrete concrete;
#endif
Base* const g_global = &concrete;
使用:
现在,您的函数不需要是虚拟的。解决这一问题的最简单方法是使需要访问受限方法的类成为具体类的朋友。然后他们可以完全访问该类,而其他人只能访问公共类 如果这不实际,您可以将实现放在基类中,并保护所有受限制的方法,然后派生一个公开受保护方法的特殊类
class Concrete
{
public:
void foo() { ... }
protected:
void bar() { ... }
};
class ConcretePrivate : public Concrete
{
public:
void bar() { Concrete:: bar(); }
};
ConcretePrivate g_globalPrivate;
Concrete& g_global = g_globalPrivate;
使用
g_global
的代码只能访问具体方法。使用g_globalPrivate
的代码可以访问具体的私有方法。这是Doom 3源代码()中使用的方法,例如neo/framework/FileSystem。h定义如下:
extern idFileSystem * fileSystem;
idFileSystemLocal fileSystemLocal;
idFileSystem * fileSystem = &fileSystemLocal;
neo/framework/FileSystem.cpp定义如下:
extern idFileSystem * fileSystem;
idFileSystemLocal fileSystemLocal;
idFileSystem * fileSystem = &fileSystemLocal;
我能找到的关于它的唯一讨论是:
idTech4高级对象都是带有虚拟方法的抽象类。这通常会影响性能,因为在运行时调用vtable之前,必须在vtable中查找每个虚拟方法地址。但有一个“诀窍”可以避免这种情况
由于在数据段中静态分配的对象具有已知类型,因此当调用公共本地方法时,编译器可以优化vtable查找
这可能与编译器有关,您应该能够通过查看编译器生成的程序集来验证这一点。如果指针总是指向同一个对象,为什么要使用指针?@BoPersson,我现在可以想到一个好的应用程序,例如,跨平台库只向用户公开抽象接口,而后端根据某些定义为各种平台使用多个实现。这当然需要对每个具有不同定义集的平台进行多次编译,但对于最终用户来说,这并没有什么区别。如果Win32Concrete类对某些.h的客户端不可见,那么他们就不太可能优化vtable,因为他们看不到它“真正是什么”,因为直到链接时间你才决定。无论如何,vtable优化是不能保证的,所以除了首先没有vtable之外,没有办法强制它。全局变量以这种方式初始化的目的是避免将它作为参数传递给需要的函数,所以这种情况不会发生。它是全局变量,因此可以在任何地方访问,因此您提到的“contex”在我的理解中不会改变。@AdrianLis如果您直接访问全局变量,那么为什么要创建
Base*const g_object
?。您可以在\u stack\u concrete.foo()上使用,
。在这种情况下,您可以避免查找,因为具体类可以向用户公开不同的(附加的)接口,而用户不应该直接使用这些接口。这就是为什么指向基类的指针将接口限制在重要的东西上。我会修改这个问题以显示可能的用法。我知道这样的解决方案,但主席先生,你又错过了一个重要的外部曝光点。Base是库用户的公共接口。具体的实现是库的内部实现的细节,每个实现都可能有很大的不同。在内部,我当然会在需要“更丰富”接口的地方使用具体变量,但无论内部实现使用什么,用户都应该获得统一的接口,因为这在运行时不会改变,并且是以这种方式编译的,问题是v表查找是否不可避免。当然,我可以使用适配器或包装器来实现具体的实现,这将限制接口,但这将需要为每个新实现执行大量预处理器宏和额外的编码。在这种情况下,使用运行时多态性更容易,但不知何故,虚拟函数会产生不必要的成本。这就是为什么我问我读到的关于可行方案的解决方案是什么