首页 > 解决方案 > std::unique_ptr::reset 和对象特定的删除器

问题描述

想象一个Deleter必须和它的对象呆在一起的东西,因为它有点特定于它的对象。就我而言,这是因为删除器使用了一个分配器库,该库在释放内存时需要知道对象的大小。由于继承,我不能简单地使用sizeof(T),而是需要在创建对象时将派生对象的大小存储在删除器中。

template<typename T>
struct MyDeleter {

   size_t objectSize;

   MyDeleter() : objectSize(sizeof(T)) {}
   template<typename S>
   MyDeleter(const MyDeleter<S>& other) : objectSize(other.objectSize) 
   // object size is correctly  transferred on assignment ^^^^^^^^^^^
  {}

   void operator(T* t) {
       t->~T();
       coolAllocatorLibrary::deallocate(t, objectSize); 
   // real object size needed for deletion ^^^^^^^^^^
   }
}

template<typename T>
my_unique_ptr = std::unique_ptr<T, MyDeleter<T>>;

template<typename T, typename... Args>
my_unique_ptr <T> make_my_unique(Args&&... args) {
   T* ptr = static_cast<T*>(coolAllocatorLibrary::allocate(sizeof(T)));
   try {
      new (ptr) T(std::forward<Args>(args)...);
   } catch(...) {
      coolAllocatorLibrary::deallocate(t, sizeof(T));
      throw;
   }
   return my_unique_ptr <T>(ptr);
}

struct Base {};
struct Derived : Base { 
    uint64_t somePayloadToMakeThisClassBiggerThanItsParent; 
}

即使在继承的情况下,这也能很好地工作:我可以通过超类的指针安全地删除派生类的对象,只要首先正确设置删除器,只要使用它就可以保证make_my_unique

{
   my_unique_ptr<Base> ptr = make_my_unique<Derived>();
   // Works fine. Here, even though ptr is of type <Base> it will deallocate
   // correctly with sizeof(Derived)  
}

唯一有问题的函数是reset(),因为我可以使用这个函数来放入一个新指针,而无需交换删除器:

{
   my_unique_ptr<Base> ptr = make_my_unique<Derived>();
   ptr.reset(new Base()); // OUCH! This will delete the new Base() with sizeof(Derived)
}

那么,有什么方法可以在这里调用reset(使用非nullptr)编译时错误?这将导致一个安全的不可滥用的接口。要么我的心智模型是错误的,要么这是 的缺点std::unique_ptr,因为似乎没有办法通过完全安全的界面来支持这个用例。

我想可能有,例如,删除器的某些特征,其中删除器可能不允许使用非 nullptr 调用重置,但这样的事情似乎不存在。

我知道“核选项”只是创建一个完全自己的my_unique_ptr类,其中包含实际std::unique_ptr的,然后只公开我想要的方法,但这是更多的努力,似乎特定于对象的分配器(例如,PMR)应该得到支持std::unique_ptr

标签: c++unique-ptr

解决方案


unique_ptr 有一个成员类型,如果该类型存在pointer则等于std::remove_reference<Deleter>::type::pointer,否则T*。必须满足 NullablePointer。所以你可以尝试pointer像这样向你的删除器添加一个类型:

template<typename T>
struct MyDeleter {
   struct pointer{
       using type = T;
       pointer():_ptr(nullptr){}
       pointer(std::nullptr_t):_ptr(nullptr){}

       //this is required to support creating uniqu_ptr of base class
       template <typename U, typename = std::enable_if_t<std::is_base_of_v<T, typename U::type>, void>>
       pointer(U ptr):_ptr(ptr){}

       T* operator->(){
           return _ptr;
       }

       operator T*() const{
           return _ptr;
       }

    private:
        T *_ptr;
        friend struct MyDeleter;
        explicit pointer(T* ptr):_ptr(ptr){}
   };

   size_t objectSize;

   MyDeleter() : objectSize(sizeof(T)) {}

   template<typename S>
   MyDeleter(const MyDeleter<S>& other) : objectSize(other.objectSize)
  {}

   void operator()(pointer t) {
       t->~T();
       deallocate(t, objectSize); 
   }

   static pointer make_ptr(T* ptr){
       return pointer{ptr};
   }
};

template<typename T>
using my_unique_ptr = std::unique_ptr<T, MyDeleter<T>>;

template<typename T, typename... Args>
my_unique_ptr <T> make_my_unique(Args&&... args) {
   T* ptr = static_cast<T*>(allocate(sizeof(T)));
   try {
      new (ptr) T(std::forward<Args>(args)...);
   } catch(...) {
      deallocate(ptr, sizeof(T));
      throw;
   }
   return my_unique_ptr <T>(MyDeleter<T>::make_ptr(ptr));
}

主要思想是防止在已删除类之外构造此指针包装器。鉴于此代码,我们将拥有以下内容:

struct Base {
    virtual ~Base()=default;
    virtual void foo(){std::cout<<"foo"<<std::endl;}
    int bar{0};
};
struct Derived : Base { 
    uint64_t somePayloadToMakeThisClassBiggerThanItsParent; 
};
struct Derived2 : Base { 
    uint64_t somePayloadToMakeThisClassBiggerThanItsParent; 
    void foo(){std::cout<<"foo2"<<std::endl;}
};

int main(){
    my_unique_ptr<Base> ptr = make_my_unique<Derived>(); //works
    ptr.reset(); //works
    ptr.reset(nullptr); //works
    ptr =  make_my_unique<Derived2>(); //works;
    ptr->foo(); //works
    ptr->bar=2; //works
    *ptr = Base{}; //works if a copy constructor of Base is available
    ptr.reset(new Base()); //does not work. cannot create a pointer wrapper
    ptr.reset(new Derived()); //does not work. cannot create a pointer wrapper. Even with a exact type but created with new.
}

推荐阅读