When is static cast safe when you are using multip

2020-02-05 13:51发布

I found myself in a situation where I know what type something is. The Type is one of three (or more) levels of inheritance. I call factory which returns B* however T is either the highest level of a type (if my code knows what it is) or the 2nd level.

Anyways, I did a static_cast in the template which is the wrong thing to do. My question is WHEN can I static cast safely? Is there ever such a time? I did it in this case because I'd rather get compile errors when I accidentally have T as something wacky which (has happened and) dynamic cast ignores (and returns null). However when I know the correct type the pointer is not adjusted causing me to have a bad pointer. I'm not sure why static cast is allowed in this case at all.

When can I use static_cast for down casting safely? Is there ever a situation? Now it seems like it always is wrong to use a static_cast (when the purpose is to down cast)

Ok I figured out how to reproduce it.

#include <iostream>
struct B { virtual void f1(){} };
struct D1 : B {int a;};
struct D2 : B {int a, b; };
struct DD : D1, D2 {};

int main(){
void* cptr = new DD(); //i pass it through a C interface :(
B*  a = (B*)cptr;
D2* b = static_cast<D2*>(a); //incorrect ptr
D2* c = dynamic_cast<D2*>(a); //correct ptr
std::cout << a << " " <<b << " " <<c;
}

6条回答
爷、活的狠高调
2楼-- · 2020-02-05 13:54

A cross cast doesn't need a dynamic_cast at all..

   struct Base1 { virtual void f1(); };
   struct Base2 { virtual void f2(); };
   struct Derived : Base1, Base2 {};

   Base1* b1 = new Derived();

   // To cast it to a base2 *, cast it first to a derived *
   Derived *d = static_cast<Derived *>(b1);
   Base2 *b2 = static_cast<Base2 *>(d);
查看更多
霸刀☆藐视天下
3楼-- · 2020-02-05 14:09

If Derived has Base as a public (or otherwise accessible) base class, and d is of type Derived*, then static_cast<Base*>(d) is an upcast.

This is always technically safe.

And generally unnecessary, except for cases where you have hiding (shadowing) of method.

Cheers & hth.,

查看更多
家丑人穷心不美
4楼-- · 2020-02-05 14:09

The problem lies with this line:

B*  a = (B*)cptr;

If you convert something to a void pointer, you must convert it back to the same type that it was converted from first before doing any other casts. If you have a situation where multiple different types of objects have to go through the same void pointer, then you need to first cast it down to a common type before converting to a void pointer.

int main(){
  B *bptr = new DD; // convert to common base first (won't compile in this case)
  void* cptr = bptr; // now pass it around as a void pointer
  B*  a = (B*)cptr; // now back to the type it was converted from
  D2* b = static_cast<D2*>(a); // this should be ok now
  D2* c = dynamic_cast<D2*>(a);  // as well as this
  std::cout << a << " " <<b << " " <<c;
}

EDIT: If you only know that cptr points to some object which is of a type derived from B at the time of the cast, then that isn't enough information to go on. The compiler lets you know that when you try to convert the DD pointer to a B pointer.

What you would have to do is something like this:

int main(){
  void* cptr = new DD; // convert to void *
  DD* a = (DD*)cptr; // now back to the type it was converted from
  D2* b = static_cast<D2*>(a); // this should be ok now, but the cast is unnecessary
  D2* c = dynamic_cast<D2*>(a);  // as well as this
  std::cout << a << " " <<b << " " <<c;
}

but I'm not sure if that is acceptable in your actual usage.

查看更多
叛逆
5楼-- · 2020-02-05 14:14

Just for completeness (knowing that I'm late a little, just for late readers like me...):

static_cast can be applied, if used correctly!

At first, the simple case:

struct D1 { }; // note: no common base class B!
struct D2 { };
struct DD : D1, D2 { };

You can get from D1* to D2* via intermediate downcast to DD*:

D1* d1 = new DD();
D2* d2 = static_cast<DD*>(d1);

The upcast to D2* is implicit then. This is possible even for non-virtual inheritance. But be aware that you need to be 100% sure that d1 really was created as DD when doing the downcast, otherwise you end up in undefined behaviour!

Now the more complex case: Diamond pattern! This is what is presented in the question:

void* cptr = new DD();
B* a = (B*)cptr;

Now this cast is already is dangerous! What actually is implemented here is a reinterpret_cast:

B* a = reinterpret_cast<B*>(cptr);

What you instead want is a simple upcast. Normally, one would not need a cast at all:

B* a = new DD(); //ambigous!

Solely: DD has two inherited instances of B. It would have worked if both D1 and D2 inherited virtually from B (struct D1/2 : virtual B { }; – not to be confused with B/D1/D2 being virtual classes!).

B* b1 = static_cast<D1*>(new DD());
B* b2 = static_cast<D2*>(new DD());

The cast to the respective bases D1 or D2 now makes clear which of the two inherited instances of B shall be pointed to.

You now can get back the respective other instance by downcasting to DD again; due to the diamond pattern, you need an intermediate cast again:

D2* bb1 = static_cast<DD*>(static_cast<D1*>(b1));
D1* bb2 = static_cast<DD*>(static_cast<D2*>(b2));

The very important point about all this matter is: You absolutely need use, when down-casting, the same diamond edge you used for up-casting!!!

Conclusion: Yes, it is possible using static casts, and it is the only option if the classes involved are not virtual (note: to be differed from virtual inheritance!). But it is just too easy to fail in doing it correctly, sometimes even impossible (e. g. if having stored pointers of base type to arbitrary derived types in a std::vector), so usually, the dynamic_cast solution as presented by Ben is the much safer one (provided virtual data types are available; if so, in the forementioned vector example, it is the only solution!).

查看更多
啃猪蹄的小仙女
6楼-- · 2020-02-05 14:15

A cross-cast:

struct Base1 { virtual void f1(); };
struct Base2 { virtual void f2(); };
struct Derived : Base1, Base2 {};

Base1* b1 = new Derived();
Base2* b2 = dynamic_cast<Base2*>(b1);

requires use of dynamic_cast, it cannot be done with static_cast (static_cast should have caused a compile-time error). dynamic_cast will also fail if either base class is not polymorphic (the presence of virtual functions is NOT optional).

See this explanation on MSDN

查看更多
祖国的老花朵
7楼-- · 2020-02-05 14:18

You can safely upcast if you are sure that the object is actually an instance of that class.

class Base {};
class Derived1 : public Base {};
class Derived2 : public Base {};

int main()
{
    Base* b = new Derived1;

    Derived1* d1 = static_cast<Derived1*>(b); // OK
    Derived2* d2 = static_cast<Derived2*>(b); // Run-time error - d isn't an instance of Derived2
}
查看更多
登录 后发表回答