c++ - 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
。
解决方案
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.
}
推荐阅读
- amazon-web-services - Ubuntu 18.04 上的 aws ec2 上的实例设置问题
- python - 将管道分隔的 csv 文件转换为 JSON 格式
- python - pandas - 按自定义顺序对列进行排序
- java - 从 SpringBoot JPA 存储库中的所有表中获取结果
- javascript - 在字符串上移动光标
- json - 使用 VBA 将 Outlook 邮件发送到 JIRA(作为问题)
- javascript - 如何打印 Vaadin Flow 对话框?
- cluster-analysis - 增强k的代码意味着使用红黑树的聚类算法以及如何检查它
- automated-tests - Cucumber:自定义序列化程序而不是 GherkinSerializer
- typescript - 使用 JEST 框架在测试文件中导入 apollo-server 时如何解决类扩展未定义错误