14

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

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

我什么时候可以安全地使用 static_cast 进行向下投射?有没有什么情况?现在似乎使用 a 总是错误的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;
}
4

6 回答 6

13

交叉演员:

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_caststatic_cast应该导致编译时错误)。 dynamic_cast如果任一基类不是多态的(虚函数的存在不是可选的),也会失败。

请参阅MSDN 上的此说明

于 2011-10-17T04:44:19.170 回答
5

如果Derived具有Base作为公共(或以其他方式可访问的)基类,并且d是 type Derived*static_cast<Base*>(d) 则为upcast

这在技术上总是安全的。

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

干杯&hth.,

于 2011-10-17T04:23:10.277 回答
3

问题在于这一行:

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

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

于 2011-10-17T05:09:52.280 回答
2

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

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
}
于 2011-10-17T04:23:50.173 回答
1

交叉演员根本不需要 dynamic_cast ..

   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);
于 2015-06-10T14:34:15.860 回答
1

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

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

首先,简单的案例:

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

您可以通过中间向下转换从D1*到:D2*DD*

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

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

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

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

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

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

相反,您想要的是一个简单的向上转换。通常,根本不需要演员表:

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

唯一:DD 有两个继承的 B 实例。如果 D1 和 D2都从 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));

关于这件事的非常重要的一点是:你绝对需要在向下铸造时使用与向上铸造相同的金刚石边缘!!!

结论:是的,可以使用静态转换,如果所涉及的类不是虚拟的,这是唯一的选择(注意:与虚拟继承不同!)。但是正确地做到这一点太容易失败,有时甚至是不可能的(例如,如果在 a 中存储了指向任意派生类型的基类型指针std::vector),所以通常,Ben提出的dynamic_cast解决方案安全的解决方案(提供虚拟数据类型可用;如果是这样,在前面提到的向量示例中,它是唯一的解决方案!)。

于 2018-12-06T21:39:38.457 回答