当使用多重继承时,静态转换什么时候是安全的?

发布于 2024-12-10 06:37:49 字数 859 浏览 0 评论 0原文

我发现自己处于一种知道某物是什么类型的情况。类型是三个(或更多)继承级别之一。我调用返回 B* 的工厂,但是 T 是类型的最高级别(如果我的代码知道它是什么)或第二级别。

不管怎样,我在模板中做了一个static_cast,这是错误的做法。我的问题是我什么时候可以安全地进行静态投射?有这样的时候吗?我在这种情况下这样做是因为当我不小心将 T 作为一些古怪的东西(已经发生并且)动态转换忽略(并返回 null)时,我宁愿得到编译错误。然而,当我知道正确的类型时,指针不会被调整,导致我有一个错误的指针。我根本不知道为什么在这种情况下允许静态转换。

什么时候可以安全地使用 static_cast 进行向下转换?有没有出现过这样的情况?现在看来使用 static_cast 总是错误的(当目的是向下转换时)

好吧,我想出了如何重现它。

#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;
}

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;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

好菇凉咱不稀罕他 2024-12-17 06:37:49

交叉转换:

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

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

需要使用dynamic_cast,不能使用static_cast来完成(static_cast应该会导致编译时错误)。如果任一基类不是多态的(虚拟函数的存在不是可选的),dynamic_cast 也会失败。

请参阅MSDN 上的此说明

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

风轻花落早 2024-12-17 06:37:49

如果 DerivedBase 作为公共(或以其他方式可访问)基类,并且 d 的类型为 Derived* ,则 static_cast(d)向上转换

这在技术上始终是安全的。

通常是不必要的,除非您隐藏(隐藏)方法的情况。

干杯&呵呵,

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.,

花想c 2024-12-17 06:37:49

问题在于这一行:

B*  a = (B*)cptr;

如果将某些内容转换为 void 指针,则必须在执行任何其他强制转换之前将其转换回最初转换的相同类型。如果遇到多种不同类型的对象必须通过同一个 void 指针的情况,那么您需要先将其转换为通用类型,然后再转换为 void 指针。

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;
}

编辑:
如果您只知道 cptr 指向某个在转换时派生自 B 的类型的对象,那么这还不足以继续下去。当您尝试将 DD 指针转换为 B 指针时,编译器会让您知道这一点。

你必须做的是这样的:

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;
}

但我不确定这在你的实际使用中是否可以接受。

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.

鱼窥荷 2024-12-17 06:37:49

如果您确定该对象实际上是该类的实例,则可以安全地向上转换。

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
}

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
}
枫林﹌晚霞¤ 2024-12-17 06:37:49

只是为了完整性(知道我迟到了一点,只是为了像我这样迟到的读者......):

如果使用正确,static_cast可以应用!

首先,简单的情况:

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

您可以通过中间向下转换为 DD*D1*D2*

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

向上转换为 那么 D2* 是隐式的。即使对于非虚拟继承也是可能的。但请注意,在进行向下转换时,您需要 100% 确保 d1 确实被创建为 DD,否则最终会出现 undefined行为!

现在更复杂的情况:钻石图案!这就是问题中提出的内容:

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

现在这个演员已经很危险了!这里实际实现的是reinterpret_cast:

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

您想要的是一个简单的向上转换。通常,根本不需要强制转换:

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

唯一的:DD 有两个继承的 B 实例。如果 D1D2 都从 B 继承虚拟,那么它就会起作用(struct D1/2 : virtual B { }; – 不要与作为虚拟类的 B/D1/D2 相混淆!)。

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

现在,对相应基 D1D2 的转换可以清楚地表明应指向 B 的两个继承实例中的哪一个。

现在,您可以通过再次向下转换为 DD 来取回相应的另一个实例;由于钻石图案,您需要再次进行中间铸造:

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

关于所有这一切的非常重要的一点是:在向下铸造时,您绝对需要使用与向上铸造相同的钻石边缘!

结论:是的,使用静态强制转换是可能的,并且如果所涉及的类不是虚拟的,那么它是唯一的选择(注意:与虚拟继承不同!)。但很容易无法正确执行此操作,有时甚至是不可能的(例如,如果在 std::vector 中存储了指向任意派生类型的基类型指针),因此通常, dynamic_cast 解决方案,如 Ben更安全的一个(前提是虚拟数据类型可用;如果是这样,在前面提到的向量示例,它是唯一解决方案!)。

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 down-casting 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 to 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 fore-mentioned vector example, it is the only solution!).

︶ ̄淡然 2024-12-17 06:37:49

交叉转换根本不需要动态转换..

   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);

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文