如何实现零开销控制反转

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 ?

编辑:我可以想到优化器中广泛存在的循环优化。也许这是该问题的通用版本。

Kla*_*aus 5

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

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

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。最后一个或多或少总会产生一些小的开销。

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