【问题标题】:Is it ok to subclass std::unique_ptr to transparently apply a custom deleter?可以继承 std::unique_ptr 以透明地应用自定义删除器吗?
【发布时间】:2018-04-13 17:23:46
【问题描述】:

我有一个名为A 的特定类型,我想创建多个std::unique_ptr<A, void(*)(A *ptr)> 实例,而无需每次都显式传递自定义删除器。这是我想出的:

void ADeleter(A *ptr) {
  // custom deletion logic
}

class APtr : public std::unique_ptr<A, void(*)(A *ptr)> {
  APtr() : APtr(nullptr) {}
  APtr(A *ptr) : std::unique_ptr<A, void(*)(A *ptr)>(ptr, ADeleter) {}
};

std::unique_ptr(或其他智能指针类)继承以使用自定义删除器抽象构造是否安全(甚至是一种好习惯)?

【问题讨论】:

  • 它没有回答你的问题,但我已经使用的是自定义默认删除器:namespace std { template&lt;&gt; class default_delete&lt;A&gt; { public: void operator()(A *p) { ADeleter(p); } }; }
  • @werner 双重检查标准;模板的特化不得违反对自身标准的保证。
  • @WernerHenze 在std 之外定义删除器并使用它提供别名会更安全
  • 另一种(我认为更好的)方法是编写一个函数来创建一个适当的std::unique_ptr 类型的对象。这可以是一个接受A* 的简单函数,也可以是构造A 对象的更复杂的模板函数(类似于std::make_unique)。

标签: c++ c++11


【解决方案1】:

不,std:: 中您应该继承的唯一类具有虚拟析构函数。 std的大部分设计都是模板参数作为扩展点,而不是子类化。

你想要的是

struct ADeleter {
    void operator()(A *ptr) {
      // custom deletion logic
    }
};

using APtr = std::unique_ptr<A, ADeleter>;

【讨论】:

  • 这是不同的,因为它只允许每个类使用一个自定义删除器,而在不同情况下可能需要对同一类使用不同的删除器。
  • 我认为你的第一句话的第一个子句太强了,无法投票。但是,使用删除器类和 typedef 的建议要好得多。
  • @Ivan : No. struct ADeleter2 { .... }; using APtr2 = std::unique_ptr&lt;A, ADelete2&gt;; - OP 的实现已经使指针的类型依赖于使用的删除器。
  • @MartinBonner 哎呀,对不起,尽管您提议专门化 std::default_deleter。我想我正在同时阅读上面的评论......
  • @Ivan : 不是在提议 - Caleth 是在提议(我只是同意他的观点)。
【解决方案2】:

一个可能(但绝不应该)出现的问题是,因为 std::unique_ptr doesn't have a virtual destructor 您不应该在动态类型与引用它的指针不同的对象上调用 delete(例如,删除 @987654326 @ 通过 std::unique_ptr 的指针),因为这是具有讽刺意味的 UB,因为智能指针首先会停止原始 newdelete

除此之外,我认为你的方式没有直接问题。

【讨论】:

  • 所以,只要我不使用指向智能指针的指针(APtr *),我就安全了?
  • @ThiagodeArruda 或数组,直接或间接。以及它指向有问题的基类的指针或数组,std::unique_ptr&lt;A, void(*)(A *ptr)&gt;
  • 我相信你不需要虚拟的,在这个非常特殊的情况下并没有什么不同。也就是说,您可以重载+删除 ​​new 运算符以防止最容易出现的错误 (en.cppreference.com/w/cpp/memory/new/operator_new)
  • @JVApen 这只是添加更多代码来修补一个错误的决定。 std::unique_ptr有一个模板参数专门用于这种用法
【解决方案3】:

如果你不知道它是否有虚拟析构函数,你不应该从任何类继承。当然可以,但是要小心。碰巧unique_ptr 没有虚拟析构函数。所以我建议继承使用组合。例如:

template<typename T>
class Ptr {
public:
    //TODO: here provide all needed member functions for Ptr class.
private:
    std::unique_ptr<A, void(*)(A *ptr)> ptr;
};

【讨论】:

  • 这种技术的缺点是你必须重新定义std::unique_ptr的每一个函数,这值得努力吗?
【解决方案4】:

请注意,您的问题是基于意见的。

如果您要在许多地方使用带有特定删除器的指针 - 为什么不呢。我认为这没有任何问题。

我可以给出一个建议 - 使用可调用类作为删除器。您的 APtr 实例有 2 个指针:一个指向对象,另一个指向删除函数。如果使用单独的空类作为删除器,结果将只取 1 个指针,并且能够避免在销毁时额外调用函数。

【讨论】:

    【解决方案5】:

    你不需要子类,你可以使用using指令如下:

    class MyClass; //defined elsewhere
    struct MyClass_deleter
    {
        MyClass_deleter() = default;
        void operator()(MyClass* object)
        {
            // delete or clean up object here
        }
    };
    using MyClass_pointer = std::unique_ptr<MyClass, MyClass_deleter>;
    
    // and use it as follows:
    MyClass_pointer p(new MyClass());
    

    【讨论】:

    • 这在他想使用泛型函数指针作为删除器的情况下不起作用
    • 如果函数指针每次都相同,那么你可以在自定义删除器中调用它。但是,如果您每次都需要一个动态函数指针,那么您只需执行using MyClass_pointer = std::unique_ptr&lt;MyClass, void(*)(MyClass*)&gt;; 请注意,对此的一个警告是您不能再默认初始化唯一指针,或者使用ptr = nullptr 将其设置为nullptr。 Op 的示例不需要动态函数指针,因此在这种情况下不会存在此问题。
    【解决方案6】:

    你可以这样做:

    template <typename T>                                                                                                                                                                                                                              
    using unique_ptr_with_my_deleter = std::unique_ptr<T, my_deleter>;
    //[...]
    unique_ptr_with_my_deleter<int> u(new int(0));
    

    请注意,您需要一个兼容 C++11 的编译器才能正常工作。

    【讨论】:

    • 我喜欢这个解决方案!但它有点不完整,我必须做出飞跃并假设 my_deleter 是函数指针类型别名,并且函数指针/lambda 可以传递给 unique_ptr_with_my_deleter&lt;int&gt; s ctor,如果它更完整,我会赞成 :)
    • Paul B 有一个更完整的例子。将其与我的模板化使用相结合,您就可以开始使用了。 :)
    猜你喜欢
    • 2021-03-25
    • 2013-03-30
    • 1970-01-01
    • 1970-01-01
    • 2015-04-09
    • 1970-01-01
    • 2018-01-31
    • 2018-01-02
    • 2017-05-15
    相关资源
    最近更新 更多