SO是一场狗屎秀。感谢您的搭车。
简短回答:你是对的。仅当
pValues
的类型为 T[N]
时,转换才是安全的,并且您提到的两种情况(不同大小、动态分配的数组)很可能会导致 未定义的行为。
static_cast
的好处是在编译时进行了一些额外的检查,所以如果你似乎做错了什么,编译器会抱怨它(与丑陋的C风格转换相比,它允许你做几乎任何事情) ,例如:
struct A { int i; };
struct C { double d; };
int main() {
A a;
// C* c = (C*) &a; // possible to compile, but leads to undefined behavior
C* c = static_cast<C*>(&a);
}
会给你:
invalid static_cast from type ‘A*’ to type ‘C*’
在这种情况下,您转换为
void*
,从可以在编译时进行的检查的角度来看,这对于几乎任何内容都是合法的,反之亦然:void*
也可以转换回几乎任何内容,这使得static_cast
的使用一开始就完全没用,因为这些检查变得毫无用处。
对于前面的例子:
C* c = static_cast<C*>(static_cast<void*>(&a));
并不比:
更好C* c = (C*) &a;
并且很可能会导致该指针的错误使用和未定义的行为。
换句话说:
A arr[N];
A (&ref)[N] = *static_cast<A(*)[N]>(&arr);
很安全,很好。但一旦你开始滥用
static_cast<void*>
,就根本无法保证实际会发生什么,因为即使是这样的东西:
C *pC = new C;
A (&ref2)[N] = *static_cast<A(*)[N]>(static_cast<void*>(&pC));
成为可能。
自 C++17 起,至少显示的表达式不安全,即使
pValues
是指向数组第一个元素的指针并且数组具有完全匹配的类型(包括 excat 大小),无论是否从变量获取声明或致电 new
。 (如果不满足这些标准,无论以下情况如何,都是 UB。)
数组及其第一个元素不是 指针可互转换,因此
reinterpret_cast
(相当于两个 static_casts
到 void*
)无法将一个的指针值转换为另一个的指针值。
因此
static_cast<T(*)[N]>(static_cast<void*>(pValues))
仍将指向数组的第一个元素,而不是数组对象本身。
由于类型/值不匹配,因此引用此指针是未定义的行为。
这可以通过
std::launder
来解决,这可能会改变 reinterpret_cast
无法改变的指针值。具体来说,以下内容可能是明确定义的:
T (&values)[N] = *std::launder(static_cast<T(*)[N]>(static_cast<void*>(pValues)));
或同等程度
T (&values)[N] = *std::launder(reinterpret_cast<T(*)[N]>(pValues));
但前提是
std::launder
返回的指针不能用于访问无法通过原始 pValues
指针访问的任何字节。如果数组是一个完整的对象,则满足这一点,但是例如如果数组是二维数组的子数组则不满足。
有关确切的可达性条件,请参阅 https://en.cppreference.com/w/cpp/utility/launder。