从 unique_ptr<T> 的 void* 转换为 T** 是如何工作的?

How does casting from a void* of unique_ptr<T> to T** work?

我对我在 VC++ 2015 年看到的结果感到惊讶,需要帮助了解它是如何工作的。

struct MyType
{
  MyType(int x_) : x(x_) { }
  int x;
};

auto u = std::make_unique<MyType>(10);
void* pv = &u;

这显然失败了,因为 u 的地址不是指向 MyType 的指针:

MyType *pM = (MyType*)pv;

但这行得通,pM2 获取存储在 u 中的 MyType 对象的地址:

MyType** ppM = (MyType**)pv;
MyType* pM2 = *ppM;

标准中是否有任何内容表明这应该有效?或者它只是由于我的编译器的不可移植的实现细节而起作用?能让我把 unique_ptr 当作一个指向指针的指针来处理吗?

在你说 "that's stupid, don't use void* or C-style casts" 之前,请理解我正在使用遗留代码,这些代码通过 void 指针和结构成员的偏移来处理结构的序列化。我现在无法更改该部分。但是我想对结构成员使用 unique_ptr 来简化内存所有权和清理。我想知道我的 unique_ptr 在这个遗留环境中有多脆弱。

这是碰巧起作用的未定义行为,因为唯一指针碰巧仅存储一个指针作为其状态,而该状态是指向 T.

的指针

未定义的行为可以做任何事情,包括时间旅行和格式化硬盘。我知道有人这么说,其他人认为这是个玩笑,但这些实际上是可以通过实验验证的真实陈述。

碰巧,你这里的未定义行为以 "works".

的方式重新解释了一些记忆

您不能 serialize/deserialize 非 pod 结构以定义的方式使用您的库。你可以破解它来工作,但任何编译器更新(甚至是编译器标志更新!)的行为可能会突然完全不同。

考虑为 serialization/deserialization 使用一个结构,另一个用于运行时使用。马歇尔从一个到另一个。是的,这很糟糕。

基本上你在做这样的事情:

std::unique_ptr<MyType> up = ...;
MyType* p = *reinterpret_cast<MyType**>(&up);

有一些弯路和 C 风格的转换。您将指向 unique_ptr 的指针重新解释为指向 MyType

的指针

这纯属运气,会导致未定义的行为,您不应出于任何原因使用此类代码。如果您需要内部指针,请在 unique_ptr.

上使用 get() 方法

这基本上只是你的运气。

在你的特定编译器的ABI中,存储unique_ptr维护的对象的T*是对象的第一个成员,所以它与对象本身具有相同的地址。与此示例的方式大致相同:

struct container {
    int val;
};

int main() {
    container c{15};

    intptr_t val1 = reinterpret_cast<intptr_t>(&c);
    intptr_t val2 = reinterpret_cast<intptr_t>(&(c.val));

    assert(val1 == val2); //will pretty much always be true
}

当然,这不是您应该依赖的行为!标准未指定它,并且如果供应商决定他们有更好的格式来在 std::unique_ptr.