如何实现零开销的控制反转
How to implement zero-overhead Inversion of Control
几乎每个 OOP 程序员都接触过控制反转的概念。在 C++ 中,我们可以使用动态回调(即仿函数,如 lambda 和函数指针)来实现该原则。但是如果我们在编译时知道我们要将什么过程注入驱动程序,理论上我相信有一种方法可以通过将回调和 driver/signal/what-so-ever 函数组合成一个“展开的”来消除函数传递和调用的开销程序”。这是一个例子。
对于 GUI 程序,我们有关于 window 1) 设置、2) 循环和 3) 终止的逻辑。我们可以在 1) window 设置之后,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 的形式出现可以静态地(即在编译时)转换为 snippet 1?
编辑:我能想到优化器中广泛存在的循环优化。也许这是该问题的一般化版本。
“零开销”&“但是如果我们在编译时知道要将什么过程注入到驱动程序中,”是可能的。
您可以使用模板 class 传递要调用的函数:
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
的东西。最后一个或多或少总是产生一些小的开销。
但请记住:一个简单的取消引用指针通常根本不是速度问题。更重要的是,在这种情况下,无法传播常量,无法内联代码,因此不再可能进行整体优化。但如果需要运行时的灵活性,就会有一些成本。一如既往:优化前测量!
几乎每个 OOP 程序员都接触过控制反转的概念。在 C++ 中,我们可以使用动态回调(即仿函数,如 lambda 和函数指针)来实现该原则。但是如果我们在编译时知道我们要将什么过程注入驱动程序,理论上我相信有一种方法可以通过将回调和 driver/signal/what-so-ever 函数组合成一个“展开的”来消除函数传递和调用的开销程序”。这是一个例子。
对于 GUI 程序,我们有关于 window 1) 设置、2) 循环和 3) 终止的逻辑。我们可以在 1) window 设置之后,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 的形式出现可以静态地(即在编译时)转换为 snippet 1?
编辑:我能想到优化器中广泛存在的循环优化。也许这是该问题的一般化版本。
“零开销”&“但是如果我们在编译时知道要将什么过程注入到驱动程序中,”是可能的。
您可以使用模板 class 传递要调用的函数:
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
的东西。最后一个或多或少总是产生一些小的开销。
但请记住:一个简单的取消引用指针通常根本不是速度问题。更重要的是,在这种情况下,无法传播常量,无法内联代码,因此不再可能进行整体优化。但如果需要运行时的灵活性,就会有一些成本。一如既往:优化前测量!