繁体   English   中英

使用unique_ptr和自定义删除器自动扣除

[英]Using automatic deduction with unique_ptr and custom deleter

我目前正在使用一个定义了许多数据类型的C库,所有这些数据类型都需要由用户管理它们的生命周期。 以这种方式定义了许多函数:

int* create() {
    return new int();
}

void destroy(int* i) {
    delete i;
}

创建后不需要访问其中的大多数。 他们只需要存在。 因此,我正在尝试使用在我需要它们生活的范围内声明的unique_ptr来管理它们。

这就是这样一个声明的样子:

// Note that I'm avoiding writing the type's name manually.
auto a = std::unique_ptr<std::decay_t<decltype(*create())>, decltype(&destroy)>{create(), &destroy};

但这个过于冗长,所以我把它封装在一个实用函数模板中:

template<typename T>
auto make_unique_ptr(T* p, void (*f)(T*)) {
    return std::unique_ptr<T, decltype(f)>(p, f);
}

以这种方式使用:

auto b = make_unique_ptr(create(), &destroy);

这看起来很不错,但是引入了一个非标准函数,除了作为某些声明的语法糖之外没有任何实际用途。 我的同事可能甚至不知道它存在,最终用不同的名称创建它的其他版本。

引入了类模板参数推导 我认为这是我的问题的完美解决方案:一种标准的方法来推导所有这些类型而不诉诸用户定义的包装器。 所以我尝试了这个:

auto c = std::unique_ptr{create(), &destroy};

与使用C ++编译器和模板的规则一样,这会失败并出现多行长错误消息。 以下是相关部分:

(...): error: class template argument deduction failed:
 auto c = std::unique_ptr{create(), &destroy};
                                            ^
(...): note: candidate: 'template<class _Tp, class _Dp> 
unique_ptr(std::unique_ptr<_Tp, _Dp>::pointer, typename std::remove_reference<_Dp>::type&&)-> std::unique_ptr<_Tp, _Dp>'
       unique_ptr(pointer __p,
       ^~~~~~~~~~
(...): note:   template argument deduction/substitution failed:
(...): note:   couldn't deduce template parameter '_Tp'
     auto c = std::unique_ptr{create(), &destroy};
                                                ^

从理论上讲 ,我可以添加一个演绎指南来处理这个:

namespace std {

template<typename T>
unique_ptr(T* p, void (*f)(T*)) -> unique_ptr<T, decltype(f)>;

}

它至少在我的gcc版本上有效,但标准不太喜欢它:

[namespace.std]

1除非另有说明,否则如果C ++程序向名称空间std或命名空间std中的命名空间添加声明或定义,则其行为是未定义的。

4如果声明,C ++程序的行为是未定义的
(......)
4.4 - 任何标准库类模板的扣除指南。

还有一些与区分指针和数组有关的问题,但让我们忽略它。

最后,问题:在使用自定义删除器时,有没有其他方法可以'帮助' std::unique_ptr (或者std::make_unique )来推断出正确的类型? 万一这是一个XY问题,是否有任何解决方案我没有想到这些类型的生命周期管理(也许是std::shared_ptr )? 如果这两个答案都是否定的,那么对于我应该期待的是否有任何改进,那么解决这个问题呢?

请随意在Coliru上测试上述示例。

作为值的类型:

template<class T>
struct tag_t {};
template<class T>
constexpr tag_t<T> tag{};

作为类型的值:

template<auto f>
using val_t = std::integral_constant<std::decay_t<decltype(f)>, f>;
template<auto f>
constexpr val_t<f> val{};

请注意, val<some_function>是一个空类型,可以使用()constexpr上下文中调用,它将调用some_function 它也可以存储int或者其他什么,但是我们将使用它来无状态地存储函数指针。

现在让我们玩得开心:

namespace factory {
  // This is an ADL helper that takes a tag of a type
  // and returns a function object that can be used
  // to allocate an object of type T.
  template<class T>
  constexpr auto creator( tag_t<T> ) {
    return [](auto&&...args){
      return new T{ decltype(args)(args)... };
    };
  }
  // this is an ADL helper that takes a tag of a type
  // and returns a function object that can be used
  // to destroy that type
  template<class T>
  constexpr auto destroyer( tag_t<T> ) {
    return std::default_delete<T>{};
  }

  // This is a replacement for `std::unique_ptr`
  // that automatically finds the destroying function
  // object using ADL-lookup of `destroyer(tag<T>)`.
  template<class T>
  using unique_ptr = std::unique_ptr< T, decltype(destroyer(tag<T>)) >; // ADL magic here

  // This is a replacement for std::make_unique
  // that uses `creator` and `destroyer` to find
  // function objects to allocate and clean up
  // instances of T.
  template<class T, class...Args>
  unique_ptr<T> make_unique(Args&&...args) {
    // ADL magic here:
    return unique_ptr<T>( creator( tag<T> )(std::forward<Args>(args)...) );
  }
}

好的,这是一个框架。

现在让我们想象你有一些图书馆。 它有一种类型。 它需要超级秘密特殊酱来创建和销毁它的实例:

namespace some_ns {
  struct some_type {
    int x;
  };
  some_type* create( int a, int b ) {
    return new some_type{ a+b }; // ooo secret
  }
  void destroy( some_type* foo ) {
    delete foo; // ooo special
  }
}

我们想把它连接起来。 您重新打开命名空间:

namespace some_ns {
  constexpr auto creator( tag_t<some_type> ) {
    return val<create>;
  }
  constexpr auto destoyer( tag_t<some_type> ) {
    return val<destroy>;
  }
}

我们完成了。

factory::unique_ptr<some_ns::some_type>是将唯一ptr存储到some_type的正确类型。 要创建它你只需要factory::make_unique<some_ns::some_type>( 7, 2 )并获得一个正确类型的唯一ptr,其中一个驱逐舰排成factory::make_unique<some_ns::some_type>( 7, 2 ) ,内存开销为零,并且对驱逐舰功能的调用涉及否间接。

基本上为factory::unique_ptrfactory::make_unique扫描std::unique_ptrstd::make_unique ,然后将创建者/驱逐舰ADL助手排成一行,使所有唯一的ptrs到达给定类型,做正确的事情。

测试代码:

auto ptr = factory::make_unique<some_ns::some_type>( 2, 3 );
std::cout << ptr->x << "=5\n";
std::cout << sizeof(ptr) << "=" << sizeof(std::unique_ptr<some_ns::some_type>) << "\n";

实例

与编写自定义唯一ptr类型相比,这没有运行时开销。 如果你没有为X的命名空间(或namespace factory )中的tag_t<X>重载creatordestroyer creator ,那么factory::make_unique将返回一个标准的标准std::unique_ptr<X> 如果这样做,它会注入编译时信息如何销毁它。

默认情况下, factory::make_unique<X>使用{}初始化,因此它适用于聚合。

tag_t系统用于启用基于ADL的factory::make_uniquefactory::unique_ptr自定义。 它在其他地方也很有用。 最终用户不必了解它,他们只需要知道你总是使用factory::unique_ptrfactory::make_unique

搜索std::make_uniquestd::unique_ptr发现有人违反此规则的情况。 最终(你希望)他们会注意到所有唯一的指针都是factory::unique_ptr而不是std

向系统添加类型的魔力足够短,并且很容易复制,人们可以在不必了解ADL的情况下进行复制。 我会在某处的评论中加入一段标有“你不需要知道这个,但这是如何工作”的段落。

这可能过头了; 我正在考虑如何使用一些新功能在处理分布式特征和破坏/创建。 我没有在生产中试过这个,所以可能会有一些未解决的问题。

您可以做的一件事是使用using语句为std::unique_ptr<T, void (*)(T*)>引入别名

template<typename T>
using uptr = std::unique_ptr<T, void (*)(T*)>;

然后你可以像使用它一样

auto u = uptr<int>{create(), &destroy};

我建议编写自定义删除器而不是使用函数指针。 使用函数指针会使所有unique_ptr的大小无缘无故。

相反,写一个模板化函数指针的删除器:

template <auto deleter_f>
struct Deleter {
    template <typename T>
    void operator()(T* ptr) const
    {
        deleter_f(ptr);
    }
};

或者,正如Yakk - Adam Nevraumont在评论中提到的那样

template <auto deleter_f>
using Deleter = std::integral_constant<std::decay_t<decltype(deleter_f)>, deleter_f>;

使用它变得非常干净:

auto a = std::unique_ptr<int, Deleter<destroy>>{create()};

虽然您可能希望将其与make_unique_ptr函数结合使用:

template <auto deleter_f, typename T>
auto create_unique_ptr(T* ptr)
{
    return std::unique_ptr<T, Deleter<deleter_f>>{ptr};
}

// Usage:
auto a = create_unique_ptr<destroy>(create());

你太复杂了。 只需将std::default_delete专门用于自定义类型 ,就可以使用vanilla std::unique_ptr

遗憾的是std::shared_ptr不使用相同的自定义点,你必须明确地提供删除器。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM