什么时候在 C++ 中向上转型是非法的?

Posted

技术标签:

【中文标题】什么时候在 C++ 中向上转型是非法的?【英文标题】:When is upcasting illegal in C++? 【发布时间】:2015-01-14 22:51:53 【问题描述】:

我很确定我理解向上转换和向下转换之间的一般区别,尤其是在 C++ 中。我知道我们不能总是向下转换,因为将基类指针转换为派生类指针会假定所指向的基类对象具有派生类的所有成员。

学期初,我的教授告诉全班同学,有时在 C++ 中向上转换也是非法的,但我似乎在笔记中忘记了原因,我不记得这是什么时候发生的。

【问题讨论】:

我不确定,但也许它与事实有关,Class可以从多个类继承,所以“父类”不是唯一的? @IvanKuckir:在演员阵容中,您总是指定目标类型。因此,您可以从多个类继承并不重要。编译只检查目标类型是否在基类中。 的问题是当您通过两个不同的路径间接从同一类型继承时。 【参考方案1】:

如果“非法”是指格式错误,那么如果基类不可访问或不明确,则属于非法。

例如,当基类是私有的时,它是不可访问的。

class A ;
class B : A ;
...
B b;
A *pa = &b; // ERROR: base class is inaccessible

请注意,即使在 C++11 中,C 样式转换也可以“突破”访问保护并执行形式上正确的向上转换

A *pa = (A *) &b; // OK, not a `reinterpret_cast`, but a valid upcast

当然应该避免这种用法。

如果您的源类型包含多个目标类型的基本子对象(通过多重继承),则存在歧义。

class A ;
class B : public A ;
class C : public A ;
class D : public B, public C ;

D d;
A *pa = &d; // ERROR: base class is ambiguous

在这种情况下,可以通过显式地“走”所需的上播路径来执行上播,其中包含中间上播到基不再模棱两可的点

B* pb = &d;
A* pa = pb; // OK: points to 'D::B::A' subobject

【讨论】:

为什么要“不惜一切代价避免”,为什么这是“当然”? @Lightness Races in Orbit:因为这是一种破坏语言基本原则的黑客行为。当基础无法访问时,该语言的作者面临着一个艰难的选择:要么将其设为reinterpret_cast(如果基础访问发生更改,这将产生潜在的微妙且几乎无法检测的错误)或使其成为保护失效的@987654326 @(这是他们选择的)。这两种变体都很可怕。 IMO,“不惜一切代价”夸大了情况。是的,通常应该避免这种情况,但也有更糟糕的情况,所以如果归结为选择,一些成本可能是可以接受的。 @Lightness Races in Orbit:我不知道为什么他们在这种情况下没有把它弄成病态的。显然,他们希望保留 C 风格转换的遗留属性,这对于指针转换“总是有效”。 @AndreyT:两个最新的标准中引入了一些特性和结构,这些特性和结构违背了语言的基本原则。除了最可怕的黑客攻击之外,我不会购买“不惜一切代价避免”的论点。无论如何,我评论的重点是,您在回答中没有在该断言旁边写下任何理由;也许你现在可以这样做?【参考方案2】:

如果基类不明确(通过不同的路径继承了两次或多次),那么您无法一步完成向上转型。

如果基类不可访问,那么向上转换的唯一方法是使用 C 样式转换。这是该演员阵容的一个特例,它是唯一可以完成这项工作的演员。本质上,它的行为类似于static_cast,不受可访问性的限制。


标准。

C++11 §5.4/4:

... 在 [a C cast] 中执行static_cast 在以下情况下转换 即使基类不可访问也是有效的:

指向派生类类型的对象或派生类类型的左值或右值的指针可以显式 分别转换为指向明确基类类型的指针或引用; 指向派生类类型成员的指针可以显式转换为指向类成员的指针 明确的非虚拟基类类型; 指向明确的非虚拟基类类型的对象的指针,明确的左值 非虚拟基类类型或指向明确非虚拟基类类型成员的指针可以显式转换为指针、引用或指向派生类类型成员的指针, 分别。

歧义示例:

struct Base ;
struct M1: Base ;
struct M2: Base ;
struct Derived: M1, M2 ;

auto main() -> int

    Derived d;
    //static_cast<Base&>( d );                      //! Ambiguous
    static_cast<Base&>( static_cast<M2&>( d ) );    // OK


无法访问的基础示例,(通常)在演员表中进行地址调整:

struct Base  int value; Base( int x ): value( x )  ;

class Derived
    : private Base

public:
    virtual ~Derived()        // Just to involve an address adjustment.
    Derived(): Base( 42 ) 
;

#include <iostream>
using namespace std;

auto main() -> int

    Derived d;
    Base& b = (Base&) d;
    cout << "Derived at " << &d << ", base at " << &b << endl;
    cout << b.value << endl;
;

【讨论】:

+1 尤其是第二个例子。虽然我会从本质上删除,因为您给出了无法访问的基地的演员阵容的确切定义。 @Deduplicator:感谢您的评论。 “本质上”是一个狡猾的词,涵盖两种情况。 (1) cast 表示法(C cast)可以添加const_cast。 (2) 在指定类型不是基类且不是派生类或相同类的情况下,它会愉快地执行reinterpret_cast(也可能添加const_cast),作为向灾难方向迈出的一步.不知道我是否应该包括这些细节。 离题了,但是你为什么写auto main() -&gt; int 而不是int main()?我不明白当返回类型无论如何都明确说明时(并且不依赖函数参数的任何decltype),为什么要使用自动类型推导语法。只是想了解您是否有一些我错过的微妙见解。 @Cornstalks:除了auto 语法之外,我认为没有理由使用 C++11 之前的函数声明语法。无论如何都必须使用auto 语法。除了 (1) 是函数声明的单一通用语法之外,它 (2) 允许您每次都在同一位置查看函数名称,以便于扫描代码,以及 (3) 在返回类型规范中使用非限定类型,对于成员函数。仍然我特例main,在同一行写返回类型,我用旧语法声明void函数,类似于Pascalprocedure【参考方案3】:

在 C++ 中有两种情况下向上转换是不正确的(在编译时诊断):

    有问题的基类不可访问

    class base ;
    class derived : base ;
    
    int main() 
        derived x;
        base& y = x; // invalid because not accessible.
        // Solution: C-style cast (as static_cast without access-check)
        base& y1 = (base&)x;
    
    

    所讨论的基类子对象不是明确的

    class base ;
    struct A1 : base ;
    struct A2 : base ;
    
    struct derived : A1, A2 ;
    int main() 
        derived x;
        base& y = x; // invalid because ambiguous.
        // Solution 1, scope resolution:
        base& y1 = static_cast<A1::base&>(x);
        base& y2 = static_cast<A2::base&>(x);
        // Solution 2, intermediate unambiguous steps:
        A1& a1 = x;
        A2& a2 = x;
        base& ya1 = a1;
        base& ya2 = a2;
    
    

【讨论】:

以上是关于什么时候在 C++ 中向上转型是非法的?的主要内容,如果未能解决你的问题,请参考以下文章

C++将派生类赋值给基类(向上转型)

Java中ArrayList的向上转型问题(父类与子类的关系),求大神解决

Object 向下转型

Java转型(向上转型和向下转型)

11 向上与向下转型

对象的转型