C++中内联函数包装器的限制

Limits to inlining function wrappers in C++

本文关键字:包装 函数 C++      更新时间:2023-10-16

我的问题涉及C++中函数包装器的内联优化应用,请考虑以下代码,WorkerStructure对象是用封装一些功能块的函数包装器初始化的。然后在调用WorkerStructure::doSomeWork方法时使用函数包装器。

workerFunction对象封装的功能在WorkerStructure::doSomeWork方法上应用时是否会内联?,显然,如果该功能是在其他转换单元中定义的,workerFunction对象只封装了一个函数指针,那么是否存在其他无法内联的情况

当在不同的转换单元中定义的lambda函数通过函数包装器传递时,它是否等效于传递函数指针?

struct WorkerStructure
{
WorkerStructure(std::function <bool(float)> &f):workerFunction(f) {}
void doSomeWork(float inputValue)
{
if(workerFunction(inputValue))
{
//do some conditional operation
}
}
std::function <bool(float)> workerFunction ;
};

std::function的多态性本质上使实际内联调用变得非常困难。由于CCD_ 2可以报道任何可调用实体;您将如何编写内联代码?

这有点像内联虚拟函数,这些函数是在没有其他可用信息的情况下通过基指针调用的(也就是说,在调用之前没有从派生到基指针的赋值,编译器可能会使用它来启用内联)。

大多数情况下,std::function是用一个void*指针和一个指向模板化函数专门化的函数指针来实现的,该函数执行实际的调用和强制转换等操作。当然,也有一些变体使用虚拟函数来实现这一点,它们更清楚地说明了为什么这非常困难。即使是链接时间的阿片化也无能为力,因为这无关紧要,你已经拥有了在呼叫站点上可以获得的所有信息(这并不多)。

以下是std::function非常的粗略版本,使用指向模板的指针函数版本,只处理存储和调用方面(忽略内存管理、复制、移动、重置、空间优化等):

template<class Sig>
class function;
template<class R, class... Args>
class function<R(Args...)>{
typedef R (*call_type)(void*, Args...);
void* _obj;
call_type _caller;
public:
template<class F>
function(F f)
: _obj(new F(f))
, _caller([](void* p, Args... args){ return (*static_cast<F*>(p))(args...); })
{}
R operator()(Args... args) const{
return _caller(_obj, args...);
}
};

活生生的例子。我认为很难检查_obj_caller内部的实际内容以及调用function的点。

这里是带有虚拟函数的版本,仅供参考。

有趣的是,我今天刚刚在邮件列表上询问了Clang/LLVM中虚拟函数的内联问题。std::function的动态特性使它本质上是一个虚拟调用,因为virtual调用只不过是指向函数的指针。

以LLVM为例,让我们玩以下程序:

#include <cstdio>
typedef void (*Function)();
void donothing() {}
void print() { printf("Hello World!"); }
Function get(int i) {
if (i % 2 == 0) { return donothing; }
return print;
}
int main() {
Function f = get(0);
f();
}

发射的主要功能:

define i32 @main() uwtable readnone {
ret i32 0
}

因此,编译器能够理解哪个函数被选中(通过内联和常量传播的组合),并内联调用。

不幸的是,我在电子邮件中证明了通过虚拟表传递是不起作用的(优化器不知何故丢失了信息,无法内联调用)。因此,尽管内联完全有可能通过std::function工作,但它可能不仅取决于编译器,还取决于您碰巧使用的std::function的特定实现。恐怕您需要试用您的应用程序。