为什么静态向下转换 unique_ptr 不安全? [英] Why is static downcasting unique_ptr unsafe?

查看:28
本文介绍了为什么静态向下转换 unique_ptr 不安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我指的是向下转换"的后续问题unique_ptr到 unique_ptr<Derived> 这本身对我来说似乎很有意义.

I am referring to a follow-up question to "Downcasting" unique_ptr<Base> to unique_ptr<Derived> which seems to make sense to me in itself.

OP 要求从 unique_ptr 中获取 unique_ptr,其中后一个对象是动态类型 Derived,这样静态向下转换是安全的.

OP asks for getting a unique_ptr<Derived> out of a unique_ptr<Base>, where the latter object is of dynamic type Derived, so that the static downcast would be safe.

通常情况下(如互联网上 95% 的解决方案所建议的,粗略估计),简单的解决方案是:

Normally, (as 95% of solutions in the Internet suggest, roughly estimated), the simple solution would be:

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

OP 也声明了

附注.更复杂的是,一些工厂驻留在运行时动态加载的 DLL 中,这意味着我需要确保生成的对象在同一时间销毁创建它们时的上下文(堆空间).所有权的转移(这通常发生在另一个上下文中)然后必须提供一个从原始上下文中删除.但除了必须提供/连同指针一起投射删除器,投射问题应该是一样.

PS. There is an added complication in that some of the factories reside in DLLs that are dynamically loaded at run-time, which means I need to make sure the produced objects are destroyed in the same context (heap space) as they were created. The transfer of ownership (which typically happens in another context) must then supply a deleter from the original context. But aside from having to supply / cast a deleter along with the pointer, the casting problem should be the same.

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

Now, the solution seems to be to get the deleter from the unique_ptr<Base> object and pass it to the new object, which clearly results in unique_ptr<Derived, default_delete<Base>>. But default_delete is stateless anyways. The only difference is the template argument. But since we always declare dtor virtual when using inheritance with dynamic polymorphism in C++, we always call ~Derived anyways, so I would think, the original deleter would be safe anyways, allowing for a clean cast to unique_ptr<Derived> (without unhandy second template argument which forbids any usual storage).

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

So, while I understand that we have two heap spaces when using a library (DLL, .dylib, ...) that creates an object and passes it to some executable, I do not understand how copying/moving a stateless deleter from the old object solves this issue.

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

Does it even solve the issue? If yes, how? If not, how can we solve this issue?

--- 另外... get_deleter 返回对位于旧 unique_ptr 中的对象的引用,当这个旧 unique_ptr 被销毁时代码>被破坏了,不是吗?--- (愚蠢的问题,因为 unique_ptr 与删除器一起明显移动)

--- Also... get_deleter returns a reference to an object lying in the old unique_ptr which is destroyed when this old unique_ptr is destroyed, is it not? --- (stupid question because unique_ptr along with deleter is clearly moved)

推荐答案

它甚至解决了问题吗?

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

You're right, it doesn't (on its own). But that isn't because the default deleter is stateless, but because its implementation is inline.

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

If not, how can we solve this issue?

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

We have to make sure that the call to delete happens from the module from which the object was originally allocated (let's call it module A). Since std::default_delete is a template, it is instantiated on-demand and an inline version is called from module B. Not good.

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

One solution is to use a custom deleter all the way through. It doesn't have to be stateful, as long as its implementation resides in module 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 导入/导出模板实例在那里有描述).

(Importing/exporting template instantiations from DLLs is described there).

此时,我们只需要从模块A返回std::unique_ptr>,并一致地转换为std::unique_ptr>.

At this point, we just have to return std::unique_ptr<Base, ModuleADeleter<Base>> from module A, and convert consistently to std::unique_ptr<Derived, ModuleADeleter<Derived>> as needed.

注意 ModuleADeleter 仅在 Base 具有非虚拟析构函数时才需要,否则只需重用 ModuleADeleter (正如链接的答案一样)将按预期工作.

Note that ModuleADeleter<Derived> is only needed if Base has a non-virtual destructor, otherwise just reusing ModuleADeleter<Base> (as the linked answer would) will work as intended.

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

The easiest solution is to used std::shared_ptr instead of std::unique_ptr. It has a bit of a performance penalty, but you don't need to implement and update a deleter, or convert it manually. This works because std::shared_ptr instantiates and type-erases its deleter upon construction, which is done inside module A. This deleter is then stored and kept until needed, and doesn't appear in the pointer's type, so you can mix pointers to objects instantiated from various modules freely.

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

Also... get_deleter returns a reference to an object lying in the old unique_ptr which is destroyed when this old unique_ptr is destroyed, is it not?

不,get_deleter 的返回值指的是包含在您调用它的 unique_ptr 中的删除器.在重载 #6 unique_ptr 之间移动时删除器状态的传输方式rel="nofollow noreferrer">这里.

No, get_deleter's return value refers to the deleter contained in the unique_ptr on which you called it. The way the deleter's state is transferred when moving between unique_ptrs is described in overload #6 here.

这篇关于为什么静态向下转换 unique_ptr 不安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆