std::unique_ptr::reset 和特定于对象的删除器
std::unique_ptr::reset and object-specific deleters
想象一个 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
class,其中包含实际的 std::unique_ptr
,然后仅公开我想要的方法,但这需要更多的努力,并且似乎 std::unique_ptr
.
应该支持特定于对象的分配器(例如 PMR)
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));
}
主要思想是防止在删除的class之外构造这个指针包装器。鉴于此代码,我们将具有以下内容:
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.
}
想象一个 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
class,其中包含实际的 std::unique_ptr
,然后仅公开我想要的方法,但这需要更多的努力,并且似乎 std::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));
}
主要思想是防止在删除的class之外构造这个指针包装器。鉴于此代码,我们将具有以下内容:
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.
}