从 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
.
我对我在 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
.