【问题标题】:Downcasting using the 'static_cast' in C++在 C++ 中使用“static_cast”进行向下转换
【发布时间】:2011-09-13 10:57:28
【问题描述】:

考虑:

class base
{
    base();
    virtual void func();
}

class derived : public base
{
    derived();
    void func();
    void func_d();
    int a;
}


main
{
    base *b = new base();
    sizeof(*b); // Gives 4.
    derived * d = static_cast<derived*>(b);
    sizeof(*d); // Gives 8- means whole derived obj size..why?
    d->func_d();
}

在上面的代码中,我将指向基对象的基指针向下转换为派生类指针。我想知道派生指针如何拥有整个派生类对象。我可以调用派生类函数(仅在派生类中声明)。我在这里没有得到这个概念。

【问题讨论】:

  • 请努力发布可编译的代码。

标签: c++


【解决方案1】:

使用static_cast 将对象转换为它实际上没有的类型会产生未定义的行为。 UB的症状差异很大。没有什么说 UB 不能让派生的成员函数被成功调用(但没有什么可以保证它会,所以不要指望它)。

这是使用static_cast 向下转换的规则,可在 C++ 标准(C++0x 措辞)的第 5.2.9 节 ([expr.static.cast]) 中找到:

类型为“指向cv1 B”的纯右值,其中B是类类型,可以转换为类型为“指向cv2的指针”的纯右值> D",其中D 是从B 派生的类,如果存在从“指向D”的有效标准转换为“指向B”的指针,cv2cv1B 的 cv 限定相同或更高 既不是D 的虚拟基类,也不是D 的虚拟基类的基类。空指针值转换为目标类型的空指针值。如果“指向 cv1 B 的指针”类型的纯右值指向 指向实际上是D 类型对象的子对象的B,结果指针指向封闭对象 D 类型。否则,强制转换的结果是未定义的。

【讨论】:

  • 我不明白,不管演员表的类型如何,这不应该根本不起作用吗?我的意思是,派生类可能有其父类不知道的方法和数据成员,那么为什么向下转换不会抛出错误?
  • @Cupidvogel:因为未定义的行为并不意味着“保证抛出异常,或以任何方式产生错误”。 UB 表示根本没有保证
  • 哦,好吧,所以你说即使我们从指向基类的指针调用派生类中声明的方法(重新解释为派生类),编译器也无法检测到这一点,并且任何异常都会在运行时被捕获?
  • @Cupidvogel 您现在可能已经知道了,但是对于检查型强制转换,您可以使用dynamic_cast&lt;&gt;,它允许检测不匹配(通过为引用类型抛出异常或返回空指针指针类型)。
【解决方案2】:

唯一进行运行时检查的演员是dynamic_cast&lt;&gt;()。如果有任何一种转换在运行时不起作用的可能性,那么应该使用这种转换。

因此从叶->根(向上转换)static_cast&lt;&gt;() 进行转换可以正常工作。
但是从 root->leaf 进行转换(向下转换)是危险的,(在我看来)应该始终使用 dynamic_cast&lt;&gt;() 来完成,因为这将依赖于运行时信息。成本微乎其微,但始终值得为安全付出代价。

【讨论】:

  • 需要注意的是dynamic_cast只有在基类定义了虚函数的情况下才比static_cast安全。
  • @Ben:如果你向下转换,如果类型不是多态的(即具有虚函数),则会出现编译时错误。如果你正在向上转换,那么它是安全的,因为它无论如何都相当于 static_cast。
  • 这意味着dynamic_cast 不能全局替换static_cast,正如您的回答所暗示的那样。 (也不是所有的沮丧,考虑 CRTP)
  • @Ben:这意味着你不能在哪里得到编译时错误。这也表明(但没有确认)你的类首先应该是多态的。我认为编译时错误没有害处。它比运行时 UB 更可取。如果您正在向下转换,那么您的设计可能有问题(或者需要指出一些事情(并且 dynamic_cast 使它突出))。无论哪种方式,dynamic_cast 都会使设计更安全;容易发现的编译时错误或运行时检查。
  • 所以可以。是的,它是对您有经验的优化。初学者应该安全并系好安全带驾驶,直到他们得到专家审查的代码,说是的,这是一个不系安全带的好地方,因为我(专家)已经审查了你不理解的异常情况.
【解决方案3】:

sizeof 存在于编译时。它既不知道也不关心在运行时,您的基础对象不指向derived。您正试图通过运行时变量影响编译时行为,这从根本上是不可能的。

【讨论】:

  • 不会sizeof 试图找到对象的实际大小,即遍历对象的所有位并确定大小,而不是从类中假设大小关联对象(此对象属于 D 类,包含 2 个 int,1 个 float 和 1 个 double,其大小必须是...)。如果它是第一个,它应该看到即使它是 D 类型,它也没有任何与方法 func_d 相关的空间,因此不应该计算它的存储空间,因为它不存在。
  • @AttitudeMonger 你的问题是什么?一个类的所有实例共享相同的大小。 sizeof 评估为编译时常量,而不是运行时“测量”。成员函数不占用类实例中的空间。虚拟指针可能会出现,这就是基于sizeof 的调整的用武之地——这同样不会阻止您调用 UB。
猜你喜欢
  • 1970-01-01
  • 2015-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
相关资源
最近更新 更多