如何制作一个 class 模板,将函数包装在一个 noexcept-detectable 可调用对象中,用作 std::unique_ptr 自定义删除器?

How to make a class template that wraps a function in a noexcept-detectable, callable object, to use as a std::unique_ptr custom deleter?

是否可以使用对象类型和自由函数作为参数来为 std::unique_ptr 创建自定义删除器?

我是模板新手,来到这里:

#include <memory>

template<typename T, typename FreeFunc> struct ObjectDeleter {
  const FreeFunc &free_func;
  ObjectDeleter(const FreeFunc &free_func) : free_func(free_func){
      
  }
  void operator()(T *item) 
  {
    if (item) {
      free_func(item);
    }
  }
};
struct Foo{};
void internal_foo_free(Foo *){}
struct Bar{};
void internal_bar_free(Bar *){}

using unique_foo_ptr = 
         std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
int main(){
    return 0;
}

错误:

<source>:19:48: error: wrong number of template arguments (1, should be 2)
   19 |          std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
      |                                                ^
<source>:3:48: note: provided for 'template<class T, class FreeFunc> struct ObjectDeleter'
    3 | template<typename T, typename FreeFunc> struct ObjectDeleter {
      |                                                ^~~~~~~~~~~~~
<source>:19:50: error: lambda-expression in template-argument only available with '-std=c++2a' or '-std=gnu++2a'
   19 |          std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
      |                                                  ^
<source>:19:87: error: template argument 2 is invalid
   19 |          std::unique_ptr<Foo, ObjectDeleter<Foo>([](Foo *foo){internal_foo_free(foo);});
      |                                                                                       ^
Compiler returned: 1

有人建议我使用函数指针(我也将其扩展为 std::function):

但这增加了通过函数指针(或std::function)添加抛出语句的可能性,编译器或静态分析器将无法检测到。使用 lambda 进行参数化将确保没有人可以在 std::unique_ptr 的析构函数中添加抛出语句。这就是我所说的“noexcept-detectable, callable object”的意思

我正在使用 C++17。

这是抱怨,因为 lambda 不能用作模板参数(无论如何在 C++20 之前)。否则,lambdas 已经 可调用 objects 除非函数 body 抛出,否则不会抛出,不需要包装器 class。你只需要做这个尴尬:

auto myDeleter = [&x](Foo* v) { internal_foo_free(v); };
std::unique_ptr<Foo, decltype(myDeleter)> guard { create_foo(), myDeleter };

最初,我将这个问题解释为“如果有人使用未标记 noexcept 的自定义删除器,我希望编译失败”。我问过这个问题,我认为这就是为什么您编辑标题以包含该措辞的原因。

noexcept 是用于优化 hint/documentation 目的的限定符。这完全取决于荣誉制度。您可以直接将其放入其中,您的源代码仍会编译(尽管静态分析器可能会抱怨)。如果你想强制自定义删除器只调用 noexcept 函数,在 C++17 中你可以使用带有 noexcept 运算符的静态断言,如果表达式调用一个 returns false non-noexcept 函数:

template <auto F>
struct NoExceptDeleter{
    template <typename T>
    void operator ()(T* arg) const noexcept {
        static_assert(noexcept(F(arg)), "deleter must be marked noexcept");
        F(arg);
    }
};

void good_delete(foo* v) noexcept { free(v); }
std::unique_ptr<foo, NoExceptDeleter<good_delete>> good_guard { make_foo() };

void bad_delete(foo* v) { throw 0; }
std::unique_ptr<foo, NoExceptDeleter<bad_delete>> bad_guard { make_foo() }; // compile-error

因为这需要一个函数指针,在 C++17 中你只能将它与 non-capturing lambdas 一起使用 + 运算符衰减为函数指针:

auto good_lambda = [](foo* v) noexcept { free(v); }
std::unique_ptr<foo, NoExceptDeleter<+good_lambda>> lambda_guard;

演示:https://godbolt.org/z/vdEov3

如果您需要在 lambda 内部进行捕获,则必须使用有状态删除器:

template <typename F>
struct StatefulNoExceptDeleter {
    F f;
    StatefulNoExceptDeleter(F f) : f(f) { }

    template <typename T>
    void operator ()(T* arg) const noexcept {
        static_assert(noexcept(f(arg)), "deleter must be marked noexcept");
        f(arg);
    }
};

/* ... */

int baz;
auto deleter = [&](Foo* t) noexcept {
    std::cout << "labmda: " << baz << std::endl;
    delete t;
};
std::unique_ptr<Foo, StatefulNoExceptDeleter<decltype(deleter)>> guard { 
    new Foo, 
    StatefulNoExceptDeleter<decltype(deleter)>{ deleter }
};