Sei*_*eun 0 c++ optimization metaprogramming compile-time
几乎每个 OOP 程序员都接触过控制反转的概念。在 C++ 中,我们可以通过动态回调(即 lambda 和函数指针等函子)来实现该原理。但是,如果我们在编译时知道要向驱动程序注入什么过程,理论上我相信有一种方法可以通过组合回调和驱动程序/信号/诸如此类的函数来消除函数传递和调用的开销进入“展开的程序”。这是一个例子。
对于 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();
Run Code Online (Sandbox Code Playgroud)
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();
Run Code Online (Sandbox Code Playgroud)
但这会带来如上所述的不必要的开销。我的问题是:我们如何利用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();
}
Run Code Online (Sandbox Code Playgroud)
但这有一个缺点,它需要静态函数。
更详细的尝试:
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();
}
Run Code Online (Sandbox Code Playgroud)
最后一种技术称为 Mixin。
如果您的编译器内联所有内容取决于很多因素。但如果被调用的函数并不是很大,通常所有调用都会被内联。
但是,如果您需要任何运行时可更改的回调,则必须使用某种可调用表示形式。这可以是函数指针或类似的东西std::function。最后一个或多或少总会产生一些小的开销。
但请记住:简单的取消引用指针通常根本不是速度问题。更重要的是,在这种情况下,无法传播常量,无法内联代码,因此无法再进行整体优化。但如果需要运行时灵活性,就会有一定的成本。一如既往:先测量再优化!