【问题标题】:C++ template to convert function into function object type将函数转换为函数对象类型的 C++ 模板
【发布时间】:2014-04-28 11:47:11
【问题描述】:

我正在尝试将 std::unique_ptr 与自定义删除器一起使用,以简化管理从各种 C API 返回给我的句柄的生命周期。这在理论上很好,但我正在努力寻找一种在运行时最佳的方法,并且没有为每种包装类型提供大量样板。

例如,考虑一些不透明类型 foo,必须通过将其指针传递给 destroy_foo 来释放它:

// approach 1: pass destroy_foo at runtime (bad for performance)
using foo_ptr = std::unique_ptr<foo, decltype(&destroy_foo)>;
foo_ptr bar{create_foo(...), destroy_foo};


// approach 2: make a deleter type (verbose - for many types)
struct foo_deleter
{
  void operator()(foo* p)
  {
    destroy_foo(p);
  }
};
using foo_ptr = std::unique_ptr<foo, foo_deleter>;
foo_ptr bar{create_foo(...)};

第一种方法对编译器来说很难优化,因为我正在传递一个函数指针,所以它被淘汰了。第二种方法似乎不必要地冗长。我有很多这样的类型我想管理,并且为每个类型手动创建一个类是很痛苦的。

如何定义一个接受destroy_foo 并为我提供与foo_deleter 等效的类型的类模板?或者是否有标准库模板可以做到这一点?

// best of both worlds - to_obj<Func> makes foo_deleter from destroy_foo...
using foo_ptr = std::unique_ptr<foo, to_obj<destroy_foo>>;
foo_ptr bar{create_foo(..)};

因此,给定任何函数,模板都会定义一个带有 operator() 的类,它只是将所有参数转发给函数,并返回结果。

【问题讨论】:

  • 当我们考虑到它用于处理资源的终止(释放内存、关闭文件句柄……)时,从存储指针进行间接调用的性能真的很重要吗?
  • 是的,它们并不总是重量级资源。除了额外的 CPU 时间之外,还有一个事实是每个指针的大小可能会翻倍。

标签: c++ templates c++11 template-meta-programming


【解决方案1】:

类似

template<typename T, void (*func)(T*)>
struct Deleter{
  void operator()(T* t) { func(t); }
};

??

或者如果你想要更强大的东西

template <typename t>
struct function_traits;

template <typename R, typename A>
struct function_traits<R (*)(A)>
{
   using t_ret = R;
  using t_arg = A;
};

template <typename F, F* f>
struct Functor
{
  using FT = function_traits<F*>;
  typename FT::t_ret operator()(typename FT::t_arg a) { 
    return f(a);
   }
};


void mydeleter(int*);
#define FUNCTOR(F) Functor<decltype(F),&F>

或者使用 C++11 的全部力量

template <typename F, F* f>
struct Functor
{
  template<typename... A>
  auto operator()(A&&... a) -> decltype(f(std::forward<A>(a)...)) {
    return f(std::forward<A>(a)...);
   }
};

#define FUNCTOR(F) Functor<decltype(F),&F>

【讨论】:

  • 我在考虑这些行,但有时函数的返回类型可能不是 void。不幸的是,使用它也需要多次编写“T”类型。即:std::unique_ptr&lt;foo, Deleter&lt;foo, delete_foo&gt;&gt; - 但也许这是最好的!
  • 好的,这是最简单的变体。您可以将函数类型传递给模板,并在其中提取返回类型和参数类型,因此您将拥有类似Deleter&lt;decltype(delete_foo),delete_foo&gt; 的内容。然后,如果您喜欢这样的东西,您可以使用宏进一步简化它。
  • 函数特征真的很有趣,但我刚刚意识到有一种方法可以避免它们。保留您的struct Functor,但将operator() 更改为可变参数模板函数,将其所有参数转发给f。即:template &lt;typename... A&gt; auto operator()(A&amp;&amp;... a) -&gt; decltype(f(std::forward&lt;A&gt;(a)...)) { return f(std::forward&lt;A&gt;(a)...); } 这不也可以吗?
  • @marack 当然你也可以这样做,我会更新答案。
【解决方案2】:

如果我正确理解您的问题,您正在寻找类似的东西吗?

#include <iostream>
#include <memory>

template <typename TYPE>
void custom_deletor(TYPE * t)
{
    delete t;
}

template <typename TYPE>
struct delete_functor
{
    void operator () (TYPE * o)
    {
        custom_deletor(o);
    }
};

template <typename TYPE>
std::unique_ptr<TYPE, delete_functor<TYPE>> make_unique(TYPE * data)
{
    return std::unique_ptr<TYPE, delete_functor<TYPE>>(data, delete_functor<TYPE>());
}


struct foo
{
    static void special_delete(foo * bar)
    {
        printf("BYE BYE \n");
        delete bar;
    }
};

template <> void custom_deletor<foo>(foo * bar)
{
    foo::special_delete(bar);
}

int main(int argc, const char * argv[])
{
    auto v = make_unique(new foo);

    return 0;
}

【讨论】:

  • 感谢您的回答,但不幸的是,这并不能解决问题。冗长只是转变为模板专业化。必须为每种类型提供专门化与必须定义我在示例中给出的小类一样糟糕。读起来也可能有点混乱。
【解决方案3】:

在 C++17 中:

template <auto F>
struct Functor
{
    template <typename... Args>
    auto operator()(Args&&... args) const { return std::invoke(F, std::forward<Args>(args)...); }
};

允许:

std::unique_ptr<char, Functor<printf>>(new char[50]{ "Hello Template Metaprogramming World!" });

【讨论】:

    【解决方案4】:

    您还可以使用 lambdas 作为创建删除函子的简写语法。

    auto foo_deleter = [](foo *ptr){ destroy_foo(ptr); };
    std::unique_ptr<foo, decltype(foo_deleter)> foo_ptr(create_foo(), foo_deleter);
    static_assert(sizeof(foo_ptr) == sizeof(void *), "No size overhead");
    

    在 C++20 中,您可以省略 deleter 参数,从而更明显地表明它没有被存储。

    还有一个想法是重载 std::default_delete 为 foo:

    // In header:
    template<>
    struct std::default_delete<foo> {
        void operator()(foo *f) const { destroy_foo(f); };
    };
    
    // Usage:
    std::unique_ptr<foo> foo_ptr(create_foo());
    static_assert(sizeof(foo_ptr) == sizeof(void *), "No size overhead");
    

    但这可能是一个坏主意,让读者感到惊讶。确保重载的std::default_delete 在范围内,否则它将使用标准删除。此外,您不能再创建指向 管理堆内存的 foo 类型的智能指针。

    【讨论】:

      【解决方案5】:

      您需要做的是使用 c++11 函数适配器 std::bindstd::mem_fn 的工具,将函数转换为函数对象。

      这是一个演示。

      #include <iostream>
      #include <string>
      #include <functional>
      #include <typeinfo>
      
      int myFun( const std::string& s, int i )
      {
          std::cout << s << '\n';
          return i * i;
      }
      
      template<typename Func, typename... TArgs>
      decltype(auto) toFunctor( Func f, TArgs&&... args )
      {
          return std::bind( f, std::forward<TArgs>( args )... );
      }
      
      int main()
      {
          using namespace std::string_literals;
          std::cout << "Func\n"s;
          std::cout << typeid( myFun( "hello"s, 5 ) ).name() << '\n';
          std::cout << "Function object\n"s;
          auto f = toFunctor( myFun, "hello"s, 5 );
          std::cout << typeid( f ).name() << '\n';
          f();
      }
      

      输出:

      Func
      int
      Function object
      class std::_Binder<struct std::_Unforced,int (__cdecl*& __ptr64)(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const & __ptr64,int),class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>
      hello
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多