【问题标题】:How to call c-style cleaner functions implicitly?如何隐式调用 c 风格的清洁器函数?
【发布时间】:2015-06-10 04:16:32
【问题描述】:

我正在研究一些 c API,我总是必须检查一些变量是否已初始化,然后使用特殊函数清除/销毁/释放它们。比如分配:

ogg_stream_state os;
ogg_stream_init(&os,ogg_page_serialno(&og));

和破坏:

ogg_stream_clear(&os);

我想自动而不是显式地调用清理函数。

【问题讨论】:

    标签: c++ c variables free destroy


    【解决方案1】:

    使用 C++ 模板,您可以轻松做到:

    template<typename ARG, typename RET>
    class Destroyer
    {
    public:
        typedef RET (*DestoyerFn)(ARG*);
        Destroyer(DestoyerFn destroyer_fn, ARG* object_ptr) { objectPointer = object_ptr; destroyerFn = destroyer_fn;}
        ~Destroyer()
        {
            if(destroyerFn && objectPointer)
                destroyerFn(objectPointer);
        }
    private:
        DestoyerFn destroyerFn;
        ARG* objectPointer;
    };
    

    ARG 是您的清理函数的参数,RET 是该函数的返回类型(RET 需要避免编译器警告。)

    示例调用:

    Destroyer<ogg_stream_state, int> des_ogg_stream(ogg_stream_clear, &os);
    

    现在你喜欢的任何地方,只要从你的函数返回,它就会调用你的清理函数。

    【讨论】:

    • 名字是必须的。否则,构造函数会创建一个临时对象,而析构函数会立即运行,而不是在作用域结束时运行。
    • 你可以去掉模板使用std::function,使用bind获取函数。然后调用看起来像Destroyer des_ogg_stream(std::bind(ogg_stream_clear, &amp;os));
    • 解决问题的不是模板,它实际上是析构函数和 RAII。但模板与 RAII 配合得很好。
    【解决方案2】:

    在现实世界的场景中,您很可能需要某种围绕 C 函数的自定义包装器,以封装它们并避开类似 C 的行为和诸如调用约定之类的怪异行为。

    在现实世界中,我不相信您可以将任何 C 代码视为“通用 C API”并设计一些可以处理所有可能的 C API 的模板类。要使这样的泛型类可行,需要考虑的事情太多了。

    例如,给定以下随机 C 代码:

    //cfile.c
    static int* something;
    
    void cfunction_init (void)
    {
      printf("C function init\n");
      something = (int*) malloc(sizeof(*something));
    }
    
    void cfunction_cleanup (void)
    {
      printf("C function cleanup\n");
      free(something);
    }
    

    你可以像这样制作一个包装类:

    class wrapper
    { 
      public:
        wrapper()  { cfunction_init(); } 
        ~wrapper() { cfunction_cleanup(); }
    }; 
    

    然后在适当的范围内简单地声明一个包装类变量:

    #include <iostream>
    
    int main()
    {
      wrapper w;
    
      std::cout << "C++ program executing" << std::endl;
    
      return 0;
    }
    

    程序输出:

    C function init
    C++ program executing
    C function cleanup
    

    【讨论】:

      【解决方案3】:

      我会考虑使用带有自定义析构函数的 shared_ptr 包装 ogg_stream_state

      class OggStreamState {
      public:
        shared_ptr<ogg_stream_state> state;
        OggStreamState() : 
          state(new ogg_stream_state, &ogg_stream_clear)
          {}
      };
      

      您的代码现在看起来像这样:

      OggStreamState os;
      ogg_stream_init(os.state.get(),ogg_page_serialno(&og));
      

      这有点难看,但这种技术为开始转向面向对象的接口而不是基于 C 函数的接口提供了一个合乎逻辑的地方。

      例如,您可以将ogg_stream_init 移动到OggStreamState,这样它就会变成

      OggStreamState os;
      os.init(ogg_page_seialno(&og));
      

      再往前走一步,重复 ogg_page,你会得到

      OggPage og = ...;
      OggStreamState os;
      os.stream_init(og.serialno());
      

      您甚至可以将 init 一直拉到构造函数中

      OggStreamState os(og.serialno());
      

      或者在极端情况下......

      OggStreamState os(og);
      

      相对于纯哨兵 RAII(如 Lundin 的解决方案)的另一个优点是,您可以毫无问题地将 OggStreamState 传入和传出函数。编译器将确定您的最后一个引用何时被销毁并为您调用 clear 函数。即你可以安全地拥有一个

      OggStreamState oss = function_that_returns_a_stream_state(...);
      

      当然,这种技术确实会引入其他开销,但通常它们很少——而且它确实会稍微模糊 ogg 流的所有权,这很多或可能不是一件好事...

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-06-12
        • 1970-01-01
        • 1970-01-01
        • 2012-07-26
        • 2020-10-04
        • 2012-08-08
        • 2018-02-24
        • 2022-11-30
        相关资源
        最近更新 更多