gcc 4.9.0不接受std::unique_ptr<void>。

3

因为 gcc 4.9.0 已经 实现了 对类型不是 voidstatic_assert,以正确符合标准。这很好,都是为了符合标准。

我有一个变体类型,将数据存储在 std::unique_ptr<void> 下,现在不起作用了。简单的解决方法是将其更改为 std::shared_ptr<void>,然后它就可以直接编译了。更好的解决方法是提供删除器函数。

以下是修复 unique_ptr 的安全方法吗?对于某些多态类型,这会表现出未定义的行为吗?

#include <memory>
#include <iostream>

template<typename T>
void Deleter(void * p) {
  delete reinterpret_cast<T*>(p);
}

int main() {
  const std::unique_ptr<void, void(*)(void*)> v(new int(199), Deleter<int>);
  std::cout << *reinterpret_cast<int*>(v.get()) << std::endl;
  return 0;
}

1
在长期来看,使用例如Boost variantBoost any会更容易且更安全,不是吗? - Some programmer dude
为什么不使用适当的变体类型(例如boost::variant或您自己的实现)?在我看来,应尽可能少地使用void* - Sander De Dycker
我有自己的变体类型,这是我实现的。我通常会使用boost variant,但我的variant需要可序列化。派生自“MurmurHash3”的类型标识号和序列化函数被剥离了。这使得该variant可以在进程/设备之间进行传递,而这正是我目前使用场景所需要的。 - Matt Clarkson
@MatthieuM.,谢谢,已经按照Jonathan的建议改用static_cast - Matt Clarkson
@MattClarkson:我想指出的是,即使使用static_cast,你仍然没有摆脱困境。在void*中,唯一有效的转换是T* -> void* -> T*。如果你将其转换回原始类型之外的任何其他类型,那么你就会遇到麻烦。 - Matthieu M.
显示剩余2条评论
1个回答

9
如果您使用unique_ptr<void,void(*)(void *)>(new X(...),Deleter<X>),那么对于任何X都是正确的,因为删除器使用对象的正确动态类型。
如果您使用了unique_ptr<void,void(*)(void *)>(new Derived(...),Deleter<Base>),那么无论是多态类型还是非多态类型,如果Base没有虚拟析构函数,或者如果Base子对象不在包含它的Derived对象的同一地址上,则会产生未定义行为。
但是,在两个地方都应该使用static_cast而不是reinterpret_cast。您应该优先使用最弱的转换来适用于给定情况,reinterpret_cast是一个大锤。
为了使代码更少出错,我会编写一个帮助函数,这样您只需命名一次类型即可,例如:
template<typename T, typename... Args>
  std::unique_ptr<void, void(*)(void*)>
  make_unique_void_ptr(Args&&... args)
  {
    using Ptr = std::unique_ptr<void, void(*)(void*)>;
    return Ptr{ new T(std::forward<Args>(args)...), Deleter<T> };
  }

我正在使用您的第一个示例。谢谢您提供的出色答案(一如既往)。 - Matt Clarkson
1
我相信第二种情况总是未定义行为,即使Base具有虚析构函数,因为您不能期望Derived*->void*->Base*表现得正常,因为通常没有保证(void*)derived == (void*)base(即使使用gcc,如果它是第一个基类,它也会有效)。 - Matthieu M.
1
@MatthieuM。说得好,但这并不意味着它总是UB,只有当(void*)derived != (void*)base时才是。 - Jonathan Wakely
可以通过使用unique_ptr<void, void(*)(void*)>(static_cast<Base*>(new Derived(...)), Deleter<Base>)来避免UB,这样只有在~Base()非虚拟时才会出现UB。但此时您可能只需使用Deleter<Derived>就可以完全避免问题 :) - Jonathan Wakely
@JonathanWakely:我认为,除非转换是 T* -> void* -> T*,否则标准将把这个循环视为未定义行为。实际上,只有当 (void*)derived != (void*)base 时,它才会出现问题,但我不愿意依赖它,因为它取决于实现(ABI)。 - Matthieu M.
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接