如何实现零开销控制反转



几乎每个OOP程序员都接触过控制反转的概念。在C++中,我们可以用动态回调(即函数,如lambda和函数指针(来实现这一原理。但是,如果我们在编译时知道我们要向驱动程序中注入什么过程,理论上我相信有一种方法可以通过将回调和驱动程序/信号/what so ever函数组合成"函数"来消除函数传递和调用的开销;展开程序";。下面是一个例子。

对于GUI程序,我们在窗口1(设置、2(循环和3(终止上有逻辑。我们可以在1(窗口设置之后、2(每个渲染循环中、3(终止之前注入代码。一种程序化的方法是以这种方式编写:

// Snippet 1:
init_window();
init_input_handler();
init_canvas();
init_socket();
while (!window_should_close()) {
update_window();
handle_input();
draw_on_canvas();
send_through_socket();
}
drop_input_handler();
drop_canvas();
drop_socket();
terminate_window();

OOP程序员以解耦和适当的抽象而自豪。相反,我们这样写:

// Snippet 2:
init_window();
on_window_init_signal.send();
while (!window_should_close()) {
update_window();
on_render_signal.send();
}
on_exit_signal.send();
terminate_window();

但如上所述,这会带来不必要的开销。我的问题是:我们如何利用C++元编程机制来实现控制的零开销反转,以便类似代码段2形式的代码可以静态地(即在编译时(转换为片段1

编辑:我能想到在优化器中广泛存在的循环优化。也许这是这个问题的广义版本。

"零开销""但如果我们在编译时知道要向驱动程序注入什么过程;是可能的。

您可以使用模板类来传递要调用的函数,如下所示:

struct SomeInjects
{
static void AtInit() { std::cout << "AtInit from SomeInjects" << std::endl; }
static void AtHandleInput() { std::cout << "AtHandleInput from SomeInjects" << std::endl; }
static void AtDraw() { std::cout << "AtDraw from SomeInjects" << std::endl; }
};
struct OtherInject
{
static void AtInit() { std::cout << "AtInit from OtherInject" << std::endl; }
static void AtHandleInput() { std::cout << "AtHandleInput from OtherInject" << std::endl; }
static void AtDraw() { std::cout << "AtDraw from OtherInject" << std::endl; }
};
template < typename Mixin >
struct Win
{
void Init()
{    
Mixin::AtInit();
}    
void HandleInput()
{    
Mixin::AtHandleInput();
}    
void Draw()
{    
Mixin::AtDraw();
}    
};
int main()
{
Win<SomeInjects> wsi; 
wsi.Init();
wsi.HandleInput();
wsi.Draw();
Win<OtherInject> wso;
wso.Init();
wso.HandleInput();
wso.Draw();
}

但这有一个缺点,那就是它需要静态函数。

更详细的尝试:

struct SomeInjects
{
void AtInit() { std::cout << "AtInit from SomeInjects" << std::endl; }
void AtHandleInput() { std::cout << "AtHandleInput from SomeInjects" << std::endl; }
void AtDraw() { std::cout << "AtDraw from SomeInjects" << std::endl; }
};
struct OtherInject
{
void AtInit() { std::cout << "AtInit from OtherInject" << std::endl; }
void AtHandleInput() { std::cout << "AtHandleInput from OtherInject" << std::endl; }
void AtDraw() { std::cout << "AtDraw from OtherInject" << std::endl; }
};
template < typename Mixin >
struct Win: Mixin
{
void Init()
{    
this->AtInit();
}    
void HandleInput()
{    
this->AtHandleInput();
}    
void Draw()
{    
this->AtDraw();
}    
};
int main()
{
Win<SomeInjects> wsi; 
wsi.Init();
wsi.HandleInput();
wsi.Draw();
Win<OtherInject> wso; 
wso.Init();
wso.HandleInput();
wso.Draw();
}

最后一种技术叫做Mixin。

如果编译器内联所有内容,则取决于许多因素。但是,如果被调用的函数不是很大,那么通常所有的调用都是内联的。

但是,如果您需要任何运行时可更改的回调,则必须使用某种可调用的表示。它可以是函数指针或类似std::function的东西。最后一个或多或少总是会产生一些小开销。

但请记住:一个简单的去引用指针通常根本不是速度问题。更重要的是,在这种情况下,常量无法传播,代码无法内联,因此无法进行整体优化。但是,如果需要运行时的灵活性,它将有一定的成本。一如既往:先测量后优化!

最新更新