【问题标题】:Make std::unique<T> compatible with std::unique<const T, CustomDeleterType>使 std::unique<T> 与 std::unique<const T, CustomDeleterType> 兼容
【发布时间】:2015-10-07 13:55:53
【问题描述】:

在代码中,我为特定对象定义了 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 最常见的情况仍然会产生编译错误,尽管直观上很清楚可以进行转换。 有没有办法解决这个限制并提示编译器函数可以从一个转换到另一个?

谢谢

【问题讨论】:

  • +1 好问题。如果模板化的移动构造函数不能处理这个,它可以处理什么,即它是做什么用的?我不确定。
  • 哦。函数 void(T*) 不能转换为 void(T const*)。我没想到。
  • 你真的需要使用std::function作为删除器吗?不同的unique_ptr 实例是否对删除器使用不同的功能,还是它们都使用相同类型的删除器?如果是后者,那么创建一个执行正确操作的自定义删除器对象会更容易,而不是在std::function 中存储一些东西。然后您可以在自定义删除器类型之间添加转换,而 std::function 无法做到这一点

标签: c++ c++11


【解决方案1】:

如果您确定删除器是正确的,您可以将 DefaultDelete 转换为您的类型:

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

与 const/non const 版本相同。但是为什么需要 2 个版本(一个用于 const,1 个用于非)尚不清楚。

【讨论】:

    【解决方案2】:

    给出的例子,

    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*) 删除器函数是必需的,并且当指针类型为T* 时也可以很好地工作。因此,不管既定惯例,这将是最实际的惯例。然而,有一次,在 C++ 首次标准化之前,通过 T const* 进行删除被认为是不自然的(并且在构造和销毁期间对象的不断变化的 const-ness 还不太清楚),以至于 delete p; 对于这样的指针无效。

    对于第一个问题,常见的解决方案是使用&lt;utility&gt; 标头中的std::move

    ConstPtrCustomDelete c1( move( a ) );
    ConstPtrCustomDelete c2( move( b ) );
    

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

    另一种解决方案是在您需要自定义删除器的情况下使用更可接受的删除器类型,即 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 ) );
    

    1const_cast 在极少数情况下存在潜在问题,即当对象最初为 const 并且删除函数 d 调用一些非-const 析构函数之前的成员函数。这可以通过使此转换支持通过模板参数的显式选择来解决。 IE。客户端代码明确保证普通的良好行为功能。

    【讨论】:

    • 因为operator() 接受Type const*,你不能在const 和非const 和非const 情况下都使用Deleter&lt;MyObject&gt;(丢失转换构造函数模板)?或者可能仍然需要允许从默认删除器类型进行转换。
    • @BenVoigt:是的,convering 构造函数用于转换std::default_delete&lt;MyObject&gt;。它还处理任意删除函数的情况,OP 指示需要使用std::function。我不确定这是多么普遍,它只是即兴代码(用 g++ 和 Visual C++ 编译)。
    • Ick,你抛弃了 const!如果数据最初是 const,比如std::make_unique&lt;const T&gt;(),我相信这是未定义的调用行为......基本上它上面的任何东西都是非 const T*。在实践中,这可能不是问题,只要你有适度的运气。让您的Deleter 使用default_delete&lt;T&gt; 并存储default_delete&lt;const T&gt; 而不是const_cast 会更清洁/更安全/足够。
    • @Yakk:析构函数是非const 成员函数。没有问题。 :)
    • @Cheersandhth.-Alf 这是一个合理的论点,但不是一个令人信服的论点。 Hmm。更进一步, expr.const.cast 导致 dcl.type.cv 导致 basic.life 导致“修改”(我找不到其定义)。我不确定delete t; 是否导致*tt 的生命周期之前被“修改”,以指向非常量类型的指针的t 结尾。但这变得很愚蠢。无论如何,如果你有一个不只是调用 delete 的清理函数,那么肯定会有危险。
    【解决方案3】:

    这应该可以解决您的问题:

    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。以上解决了这个问题。

    您还可以跳过其他两种类型的障碍来强制转换工作。但上面看起来更容易。

    【讨论】:

      【解决方案4】:

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

      您真的需要nonConstPtrCustomDelete 的不同实例来拥有共享相同签名的不同删除器,还是所有实例都使用相同的删除器?

      即你有没有这样做过:

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

      或者只有这个:

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

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

      您应该编写一个自定义删除器类型:

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

      那么你的 typedef 可以是:

      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 不会有问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-08-10
        • 1970-01-01
        • 1970-01-01
        • 2017-03-28
        • 1970-01-01
        • 2019-10-20
        • 1970-01-01
        • 2021-06-02
        相关资源
        最近更新 更多