web-dev-qa-db-fra.com

Quand l'upcasting est-il illégal en C ++?

Je suis sûr de bien comprendre la différence générale entre l'upcasting et le downcasting, en particulier en C++. Je comprends que nous ne pouvons pas toujours abattre parce que la conversion d'un pointeur de classe de base en un pointeur de classe dérivée supposerait que l'objet de classe de base pointé a tous les membres de la classe dérivée.

Au début du semestre, mon professeur a dit à la classe qu'il était parfois illégal de transposer en C++, mais il me semble avoir raté la raison dans mes notes, et je ne me souviens pas quand cela se produit.

Quand est-il illégal d'upcaster en C++?

56
Connor Foley

Si par "illégal" vous voulez dire mal formé, alors il est illégal si la classe de base est inaccessible ou ambiguë.

  • Elle est inaccessible lorsque, par exemple, la classe de base est privée.

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

    Notez que même en C++ 11, une distribution de style C peut "briser" la protection d'accès et effectuer une conversion ascendante formellement correcte

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

    Cette utilisation doit bien sûr être évitée.

  • Il est ambigu si votre type source contient plusieurs sous-objets de base du type cible (via l'héritage multiple).

    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
    

    Dans de tels cas, l'upcast peut être effectué en "parcourant" explicitement le chemin d'upcast souhaité avec des upcasts intermédiaires jusqu'au point où la base n'est plus ambiguë.

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

Si la classe de base est ambiguë (héritée deux fois ou plus via des chemins différents), vous ne pouvez pas effectuer de conversion ascendante en une seule étape.

Si la classe de base est inaccessible, la seule façon de transposer est d'utiliser une conversion de style C. C'est un cas particulier de cette distribution, c'est le seul qui peut faire le travail. Il se comporte essentiellement comme un static_cast ce n'est pas limité par l'accessibilité.


Standardese.

C++ 11 §5.4/4:

… dans [un cast C] effectuant un static_cast dans les situations suivantes, la conversion est valide même si la classe de base est inaccessible:

  • un pointeur vers un objet de type classe dérivée ou une lvalue ou rvalue de type classe dérivée peut être explicitement converti respectivement en pointeur ou en référence à un type de classe de base non ambigu;
  • un pointeur vers un membre d'un type de classe dérivé peut être explicitement converti en un pointeur vers un membre d'un type de classe de base non virtuel non ambigu;
  • un pointeur vers un objet d'un type de classe de base non virtuel non ambigu, une valeur gl d'un type de classe de base non virtuelle non ambigu ou un pointeur vers un membre d'un type de classe de base non virtuelle non ambigu peut être explicitement converti en pointeur, un référence, ou un pointeur vers un membre d'un type de classe dérivé, respectivement.

Exemple d'ambiguïté:

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
}

Exemple de base inaccessible, avec (généralement) un ajustement d'adresse dans le plâtre:

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

Il y a deux cas dans lesquels la conversion ascendante est mal formée en C++ (diagnostiquée au moment de la compilation):

  1. La classe de base en question est non accessible:

    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;
    }
    
  2. Le sous-objet de classe de base en question est pas sans ambiguïté:

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