static_cast down 一个实际上不是对象类型的类型是未定义的行为吗?
Is it undefined behavior to static_cast down a type that isn't actually the type of the object?
这是四种显式转换中的一种 discussion on Whosebug。但是我在投票最多的答案中遇到了一个问题。
引用自投票最多的 wiki 答案:
static_cast
can also cast through inheritance hierarchies. It is unnecessary when casting upwards (towards a base class), but when casting downwards it can be used as long as it doesn't cast through virtual
inheritance. It does not do checking, however, and it is undefined behavior to static_cast
down a hierarchy to a type that isn't actually the type of the object.
但是在cppref,我读了一些不那么严肃的东西:
static_cast < new_type > ( expression )
If new_type is a pointer or reference to some class D and the type of expression is a pointer or reference to its non-virtual base B, static_cast
performs a downcast. This downcast is ill-formed if B is ambiguous, inaccessible, or virtual base (or a base of a virtual base) of D. Such static_cast
makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means.
所以在 cppref 中它没有说 未定义的行为,而是说 不安全 不那么严重。
所以当我做类似的事情时:
class A{virtual foo(){}};
class B:public A{};
class C:public B{};
int main()
{
C*pc=new C;
A*pa=static_cast<A*>(pc);//Ok,upcast.
B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**
C* pc1=static_cast<C*>(pb);//downcast back to C;
}
还有一个问题,如果不是UB,是不是UB解引用pb
?
这是定义明确的行为。
A prvalue of type “pointer to cv1 B
,” where B
is a class type, can be converted to a prvalue of type “pointer
to cv2 D
,” where D
is a class derived (Clause 10) from B
, if a valid standard conversion from “pointer to D
”
to “pointer to B
” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, and
B
is neither a virtual base class of D
nor a base class of a virtual base class of D
. The null pointer value (4.10)
is converted to the null pointer value of the destination type. If the prvalue of type “pointer to cv1 B
” points
to a B
that is actually a subobject of an object of type D
, the resulting pointer points to the enclosing object
of type D
. Otherwise, the behavior is undefined.
(C++14 [expr.static.cast] (§5.9) ¶11,已强调)
pa
指向类型 A
的对象,它实际上是 B
的子对象,因此第二次转换没问题,结果指向有效的 B
.出于同样的原因,您的第三次转换是可以的(pb
指向 C
的 B
子对象)。
cppreference 表示的 "not safe" 位是关于这里没有安全网的事实:您必须通过自己的方式知道指向对象的实际动态类型是否与您的转换兼容要求;如果你弄错了,就没有 std::bad_cast
或 nullptr
- 你会得到糟糕的旧未定义行为。
cppreference 是用英文写的,目的是为了传达良好的理解,它实际上并不是规范。但我对这里的措辞没有问题:
Such static_cast
makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means.
如果你能保证前提条件,就可以了。如果您不保证前提条件,那么您的代码就不安全。代码不安全意味着什么?它的行为没有定义。
实际规格使用this wording:
If the prvalue of type “pointer to cv1 B
” points to a B
that is actually a subobject of an object of type D
, the resulting pointer points to the enclosing object of type D
. Otherwise, the behavior is undefined.
无论哪种方式,在您的示例中,您的所有转换都是有效的。
B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**
您缺少条件。沮丧本身并不是未定义的行为。如果那里实际上没有派生类型的对象,那只是未定义的行为。在这种情况下 pa
指向 C
,它是 B
,因此转换为 B
是安全的。
然而,这不是:
struct B { };
struct D1 : B { };
struct D2 : B { };
B* p = new D1;
static_cast<D2*>(p); // undefined behavior, no D2 object here
这是四种显式转换中的一种 discussion on Whosebug。但是我在投票最多的答案中遇到了一个问题。
引用自投票最多的 wiki 答案:
static_cast
can also cast through inheritance hierarchies. It is unnecessary when casting upwards (towards a base class), but when casting downwards it can be used as long as it doesn't cast throughvirtual
inheritance. It does not do checking, however, and it is undefined behavior tostatic_cast
down a hierarchy to a type that isn't actually the type of the object.
但是在cppref,我读了一些不那么严肃的东西:
static_cast < new_type > ( expression )
If new_type is a pointer or reference to some class D and the type of expression is a pointer or reference to its non-virtual base B,
static_cast
performs a downcast. This downcast is ill-formed if B is ambiguous, inaccessible, or virtual base (or a base of a virtual base) of D. Suchstatic_cast
makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means.
所以在 cppref 中它没有说 未定义的行为,而是说 不安全 不那么严重。
所以当我做类似的事情时:
class A{virtual foo(){}};
class B:public A{};
class C:public B{};
int main()
{
C*pc=new C;
A*pa=static_cast<A*>(pc);//Ok,upcast.
B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**
C* pc1=static_cast<C*>(pb);//downcast back to C;
}
还有一个问题,如果不是UB,是不是UB解引用pb
?
这是定义明确的行为。
A prvalue of type “pointer to cv1
B
,” whereB
is a class type, can be converted to a prvalue of type “pointer to cv2D
,” whereD
is a class derived (Clause 10) fromB
, if a valid standard conversion from “pointer toD
” to “pointer toB
” exists (4.10), cv2 is the same cv-qualification as, or greater cv-qualification than, cv1, andB
is neither a virtual base class ofD
nor a base class of a virtual base class ofD
. The null pointer value (4.10) is converted to the null pointer value of the destination type. If the prvalue of type “pointer to cv1B
” points to aB
that is actually a subobject of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. Otherwise, the behavior is undefined.
(C++14 [expr.static.cast] (§5.9) ¶11,已强调)
pa
指向类型 A
的对象,它实际上是 B
的子对象,因此第二次转换没问题,结果指向有效的 B
.出于同样的原因,您的第三次转换是可以的(pb
指向 C
的 B
子对象)。
cppreference 表示的 "not safe" 位是关于这里没有安全网的事实:您必须通过自己的方式知道指向对象的实际动态类型是否与您的转换兼容要求;如果你弄错了,就没有 std::bad_cast
或 nullptr
- 你会得到糟糕的旧未定义行为。
cppreference 是用英文写的,目的是为了传达良好的理解,它实际上并不是规范。但我对这里的措辞没有问题:
Such
static_cast
makes no runtime checks to ensure that the object's runtime type is actually D, and may only be used safely if this precondition is guaranteed by other means.
如果你能保证前提条件,就可以了。如果您不保证前提条件,那么您的代码就不安全。代码不安全意味着什么?它的行为没有定义。
实际规格使用this wording:
If the prvalue of type “pointer to cv1
B
” points to aB
that is actually a subobject of an object of typeD
, the resulting pointer points to the enclosing object of typeD
. Otherwise, the behavior is undefined.
无论哪种方式,在您的示例中,您的所有转换都是有效的。
B*pb=static_cast<B*>(pa);//downcast,**is it undefined or just not safe?**
您缺少条件。沮丧本身并不是未定义的行为。如果那里实际上没有派生类型的对象,那只是未定义的行为。在这种情况下 pa
指向 C
,它是 B
,因此转换为 B
是安全的。
然而,这不是:
struct B { };
struct D1 : B { };
struct D2 : B { };
B* p = new D1;
static_cast<D2*>(p); // undefined behavior, no D2 object here