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

Ice*_*ire 6 c++ unique-ptr

我指的是将“ uncasting” unique_ptr <Base>到unique_ptr <Derived>的后续问题,这本身对我来说很有意义。

OP要求unique_ptr<Derived>退出a unique_ptr<Base>,后者的对象是动态类型的Derived,因此静态向下转换将是安全的。

通常,(如Internet上95%的解决方案所示,粗略估计),简单的解决方案是:

unique_ptr<Derived> ptr(static_cast<Derived*>(baseClassUniquePtr.release()));
Run Code Online (Sandbox Code Playgroud)

OP也指出,

PS。更为复杂的是,某些工厂驻留在运行时动态加载的DLL中,这意味着我需要确保在与创建对象相同的上下文(堆空间)中销毁所生成的对象。所有权转移(通常发生在另一个上下文中)必须随后提供原始上下文中的删除器。但是除了必须提供/强制释放删除器以及指针之外,强制转换问题应该相同。

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

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

难道它甚至解决这一问题?如果是,怎么办?如果没有,我们如何解决这个问题?

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

Que*_*tin 1

它甚至能解决问题吗?

你是对的,它(本身)没有。但这并不是因为默认删除器是无状态的,而是因为它的实现是内联的。

如果不是,我们该如何解决这个问题?

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


方法一

一种解决方案是始终使用自定义删除器。它不必是有状态的,只要它的实现位于模块 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>;
Run Code Online (Sandbox Code Playgroud)

// 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>;
Run Code Online (Sandbox Code Playgroud)

(此处描述了从 DLL 导入/导出模板实例)。

此时,我们只需std::unique_ptr<Base, ModuleADeleter<Base>>从模块A返回,并一致地转换为std::unique_ptr<Derived, ModuleADeleter<Derived>>所需的。

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


方法二

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


另外...get_deleter返回对位于旧对象中的对象的引用,unique_ptr当旧对象被销毁时unique_ptr,该对象也会被销毁,不是吗?

不,get_deleter的返回值指的是unique_ptr您调用它的 中包含的删除器。在 s之间移动时删除器的状态转移方式unique_ptr重载 #6 中进行了描述。