使 std::unique<T> 与 std::unique<const T, CustomDeleterType 兼容>

Make std::unique<T> compatible with std::unique<const T, CustomDeleterType>

本文关键字:unique gt lt std CustomDeleterType 兼容 const      更新时间:2023-10-16

在代码中,我为特定对象定义了3个std::unique_ptr指针类型:

typedef std::unique_ptr<MyObject> nonConstPtrDefaultDelete;
typedef std::unique_ptr<MyObject, std::function<void(MyObject *)>>
                                                       nonConstPtrCustomDelete;
typedef std::unique_ptr<const MyObject, std::function<void(const MyObject *)>>
                                                       ConstPtrCustomDelete;

我遇到了一个用例,我需要转换nonConstPtrDefaultDelete到ConstPtrCustomDelete和nonConstPtrCustomDelete到ConstPtrCustomDelete。换句话说:

nonConstPtrDefaultDelete a;
nonConstPtrCustomDelete b;
ConstPtrCustomDelete c1(a);  // Compiler error Deleter has incompatible type
ConstPtrCustomDelete c2(b);  // Compiler error Deleter has incompatible type

主要问题来自于删除函数的类型签名不兼容。可以通过以下方式修改nonConstPtrCustomDelete类型的定义来修复nonConstPtrCustomDelete情况:

typedef std::unique_ptr<MyObject, std::function<void(const MyObject *)>>
                                                         nonConstPtrCustomDelete

然而,DefaultDelete最常见的情况仍然产生编译错误,尽管直观上很清楚转换是可能的。是否有一种方法可以解决这个限制,并提示编译器函数可以从一个转换到另一个?

谢谢

如果您确定deleter是合适的,您可以将DefaultDelete转换为您的类型:

nonConstPtrDefaultDelete a;
ConstPtrCustomDelete c1( a.release(), your_deleter );

与const/non const版本相同。但是为什么需要两个版本(一个用于const,一个用于not)还不清楚。

给出的例子,

nonConstPtrDefaultDelete a;
nonConstPtrCustomDelete b;
ConstPtrCustomDelete c1(a);  // Compiler error Deleter has incompatible type
ConstPtrCustomDelete c2(b);  // Compiler error Deleter has incompatible type

白马王子;有两个问题:

  • 不能从左值表达式构造unique_ptr。它是单一所有权,但是左值表达式要求一个副本。

  • 对于删除器,功能类型void(T*)与功能类型void(T const*)不兼容

当指针类型为T const*时需要void(T const*) delete函数,当指针类型为T*时也可以很好地工作。所以,撇开惯例不谈,这是最实用的惯例。然而,在c++第一次标准化之前,通过T const*进行删除被认为是非常不自然的(并且在构造和销毁过程中对象的常量变化也没有得到很好的理解),因此delete p;对于这样的指针是无效的。

对于第一个问题,常见的解决方案是使用<utility>头中的std::move:
ConstPtrCustomDelete c1( move( a ) );
ConstPtrCustomDelete c2( move( b ) );

对于第二个问题,一个解决方案是为每个unique_ptr配备一个自定义删除器,例如std::default_delete<T const>的实例。

另一个解决方案是在需要自定义删除器的情况下使用更容易接受的删除器类型,即1处理const -ness的删除器:

template< class Type >
class Deleter
{
private:
    function<void(Type const*)> d_;
public:
    using Mutable_type = typename remove_const<Type>::type;
    void operator()( Type const* p ) const
    {
        d_( p );
    }
    Deleter(): d_( default_delete<Type const>() ) {}
    Deleter( default_delete<Mutable_type> const& )
        : d_( default_delete<Type const>() )
    {}
    template< class D >
    Deleter( D const& d )
        : d_( [d]( Type const* p ) {
            d( const_cast<Mutable_type*>( p ) );
            } )
    {}
};

您的示例代码与move和这样一个自定义删除器,然后是

typedef std::unique_ptr<MyObject> nonConstPtrDefaultDelete;
typedef std::unique_ptr<MyObject, Deleter<MyObject>>
    nonConstPtrCustomDelete;
typedef std::unique_ptr<const MyObject, Deleter<MyObject const>>
    ConstPtrCustomDelete;
nonConstPtrDefaultDelete a;
nonConstPtrCustomDelete b;
ConstPtrCustomDelete c1( move( a ) );
ConstPtrCustomDelete c2( move( b ) );

<子>1在一种罕见的情况下,const_cast有一个潜在的问题,即当对象最初是const并且删除函数d在析构函数之前调用一些非const成员函数时,形式UB。这可以通过通过模板参数使此转换支持显式选择来解决。例如,客户端代码显式地保证了一个普通的行为良好的函数。

这应该能解决你的问题:

template<class T>
using smarter_default_delete = std::default_delete<const T>;
template<class T, class D=smarter_default_delete<T>>
using my_unique_ptr = std::unique_ptr<T, D>;
typedef my_unique_ptr<MyObject> nonConstPtrDefaultDelete;

基本上,default_delete不接受T const* s。

您还可以跳过其他两个类型的限制来强制转换工作。但是上面看起来更简单。

为什么在这里使用std::function ?

你真的需要不同的nonConstPtrCustomDelete实例有不同的delete共享相同的签名,还是所有的实例使用相同的delete ?

。你曾经这样做过吗?

void foo_deleter(MyObject*);
void bar_deleter(MyObject*);
nonConstPtrCustomDelete ptr1{ foo(), foo_deleter };
nonConstPtrCustomDelete ptr1{ bar(), bar_deleter };

或only this:

nonConstPtrCustomDelete ptr1{ foo(), foo_deleter };
nonConstPtrCustomDelete ptr1{ bar(), foo_deleter };

如果只有后者,那么使用std::function是毫无意义的,而且效率低下。如果总是使用相同的删除器,则不需要多态类型擦除函数对象。

你应该写一个自定义的删除类型:

struct non_const_deleter {
  void operator()(MyObject*) const;
};

则typepedef可以是:

typedef std::unique_ptr<MyObject, non_const_deleter>
   nonConstPtrCustomDelete;

也更容易使用,因为这个删除器可以默认构造,所以你不需要显式地提供它:

nonConstPtrCustomDelete ptr1{ foo() };

完全等价于:

nonConstPtrCustomDelete ptr1{ foo(), non_const_deleter{} };

现在您可以添加第二个删除器类型,non_const_deleter可以转换为:

struct const_deleter {
  const_deleter() = default;
  const_deleter(const non_const_deleter&) { }
  void operator()(const MyObject*) const;
};

或者让一个类型处理两种情况:

struct const_and_non_const_deleter {
  void operator()(MyObject*) const;
  void operator()(const MyObject*) const;
};

那么这两种类型可以使用相同的删除符:

typedef std::unique_ptr<MyObject, const_and_non_const_deleter>
   nonConstPtrCustomDelete;
typedef std::unique_ptr<const MyObject, const_and_non_const_deleter>
   constPtrCustomDelete;

和你不会有问题转换nonConstPtr到constPtr