为什么静态向下转换unique_ptr不安全?

Why is static downcasting unique_ptr unsafe?

本文关键字:ptr 不安全 unique 转换 静态 为什么      更新时间:2023-10-16

我指的是"Downcasting"unique_ptr

到unique_ptr的后续问题,这对我来说似乎很有意义。

OP 要求从unique_ptr<Base>中获取unique_ptr<Derived>,其中后一个对象是动态类型Derived,因此静态向下转换是安全的。

通常,(正如互联网上95%的解决方案所建议的那样,粗略估计(,简单的解决方案是:

unique_ptr<Derived> ptr(static_cast<Derived*>(baseClassUniquePtr.release()));

OP也指出,虽然

附言。还有一个额外的复杂性,因为一些工厂 驻留在运行时动态加载的 DLL 中,这意味着 I 需要确保生成的对象在同一件作品中被销毁 创建时的上下文(堆空间(。所有权转让 (通常在另一个上下文中发生(然后必须提供 原始上下文中的删除程序。但除了必须提供/将删除器与指针一起强制转换,转换问题应该是 一样。

现在,解决方案似乎是从unique_ptr<Base>对象中获取删除器并将其传递给新对象,这显然会导致unique_ptr<Derived, default_delete<Base>>。但无论如何,default_delete是无国籍的。唯一的区别是模板参数。但是由于我们在C++中使用具有动态多态性的继承时总是声明dtorvirtual,因此无论如何我们总是调用~Derived,所以我认为,原始删除器无论如何都是安全的,允许干净的强制转换unique_ptr<Derived>(没有不方便的第二个模板参数,禁止任何通常的存储(。

因此,虽然我知道在使用库(DLL,.dylib,...(创建对象并将其传递给某个可执行文件时,我们有两个堆空间,但我不明白从旧对象复制/移动无状态删除器如何解决此问题。

它甚至解决问题吗?如果是,如何?如果没有,我们如何解决这个问题?

---编辑:还...get_deleter返回对位于旧unique_ptr中的对象的引用,当这个旧unique_ptr被销毁时,该对象被销毁,不是吗?---(愚蠢的问题,因为unique_ptr与删除器一起明显被移动(

它甚至解决了这个问题吗?

你是对的,它没有(靠自己(。但这并不是因为默认删除程序是无状态的,而是因为它的实现是内联的。

如果没有,我们如何解决这个问题?

我们必须确保对delete的调用发生在最初分配对象的模块中(我们称之为模块 A(。由于std::default_delete是一个模板,因此它是按需实例化的,并且从模块 B 调用内联版本

。 不好。

方法 1

一种解决方案是一直使用自定义删除器。它不必是有状态的,只要它的实现驻留在模块 A 中。

// ModuleA/ModuleADeleter.h
template <class T>
struct ModuleADeleter {
// Not defined here to prevent accidental implicit instantiation from the outside
void operator()(T const *object) const;
};
// Suppose BUILDING_MODULE_A is defined when compiling module A
#ifdef BUILDING_MODULE_A
#define MODULE_A_EXPORT __declspec(dllexport)
#else
#define MODULE_A_EXPORT __declspec(dllimport)
#endif
template class MODULE_A_EXPORT ModuleADeleter<Base>;
template class MODULE_A_EXPORT ModuleADeleter<Derived>;

// ModuleA/ModuleADeleter.cpp
#include "ModuleA/ModuleADeleter.h"
template <class T>
void ModuleADeleter<T>::operator()(T const *object) const {
delete object;
}
template class ModuleADeleter<Base>;
template class ModuleADeleter<Derived>;

(此处介绍了从 DLL 导入/导出模板实例化(。

此时,我们只需要从模块 A 返回std::unique_ptr<Base, ModuleADeleter<Base>>,并根据需要始终转换为std::unique_ptr<Derived, ModuleADeleter<Derived>>

请注意,仅当Base具有非虚拟析构函数时才需要ModuleADeleter<Derived>,否则只需重用ModuleADeleter<Base>(如链接答案(即可按预期工作。


方法 2

最简单的解决方案是使用std::shared_ptr而不是std::unique_ptr.它有一些性能损失,但您不需要实现和更新删除器,或手动转换它。这是有效的,因为std::shared_ptr在构造时实例化并类型擦除其删除器,这是在模块 A 中完成的。然后,此删除器将被存储并保留,直到需要为止,并且不会出现在指针的类型中,因此您可以自由混合指向从各种模块实例化的对象的指针。


也。。。get_deleter返回对位于旧unique_ptr中的对象的引用,当这个旧unique_ptr被销毁时,该对象被销毁,不是吗?

否,get_deleter的返回值是指您调用它的unique_ptr中包含的删除程序。在unique_ptrs 之间移动时,删除程序状态的传输方式在此处的重载 #6 中进行了描述。