c++: dynamic_cast vs reinterpret_cast

Giorgos Keramidas keramida at ceid.upatras.gr
Tue Sep 11 00:02:08 EEST 2012


On Sun, 9 Sep 2012 20:07:52 +0300, Theodore Lytras <thlytras at gmail.com> wrote:
> Μια ερωτησούλα για όποιον ξέρει καλή C++:
>
> Υπάρχει κάποιο μειονέκτημα του dynamic_cast έναντι του reinterpret_cast όταν
> μπορούν να χρησιμοποιηθούν και τα δύο? (Δηλαδή είναι έγκυρο το cast).
>
> Υποτίθεται πως το dynamic_cast κάνει ένα run-time checking και επιστρέφει null
> αν το επιχειρούμενο cast δεν είναι σωστό. Φαντάζομαι το overhead από αυτό το
> run-time checking είναι αμελητέο, τουλάχιστον όταν μιλάμε για 2-3 casts.

Το dynamic_cast δεν κάνει μόνο «ένα run-time check», αλλά μπορεί να
επιστρέψει και ένα pointer που δείχνει «μέσα» στο αρχικό αντικείμενο.

Το κλασικό παράδειγμα για dynamic_cast είναι κάτι σαν αυτό:

    class BaseClass {
      public:
        void BaseFunction(BaseClass &) {
          cout << "This is my base class function";
        }
    }

    class DerivedClass : BaseClass {
        void HigherFunction (DerivedClass &) {
          cout << "I am hiiiiiiiiigh on emotion, hiiiiiiigh";
        }
    }

    {
      DerivedClass *derived_pointer = new DerivedClass;
      BaseClass *base_pointer = dynamic_cast<BaseClass*> derived_pointer;

      base_pointer->BaseFunction();
    }

Αν σκεφτείς λίγο τι μπορεί να είναι το memory layout ενός DerivedClass
μπορεί να μοιάζει με κάτι σαν:

    ADDRESS     +--------------------------------------+  <-- derived_pointer
                |                                      |
    0x0800C000  |  DerivedClass::HigherFunction        |
                |  0x90 0x90 0x90                      |
                |                                      |
    0x0800F000  |  +-----------------------------------+  <-- base_pointer
                |  |                                   |
                |  |  BaseClass::BaseFunction          |
                |  |  0x34 0x45 0x67 0x89 0x90 0x91    |
                |  |                                   |
                +--+-----------------------------------+

Οπότε όταν γράφεις κάτι σαν:

    BaseClass *base_pointer = dynamic_cast<BaseClass*> derived_pointer;

το type checking ελέγχει ότι όντως το 'BaseClass' είναι στο inheritance
sequence του derived_pointer και όλο χαρά σου επιστρέφει ένα pointer στο
'encapsulated' base object.

Τώρα σκέψου τι μπορεί να γίνεται με πολλαπλό inheritance και multiple
base object 'boxing' μέσα στο ίδιο derived object με κώδικα σαν αυτόν:

    class BaseClass {
      public:
        void BaseFunction(BaseClass &) {
          cout << "This is my base class function";
        }
    }

    class TrembleClass {
      public:
        void TrembleFunction(TrembleClass &) {
          cout << "This is my trembleclass function";
        }
    }

    class DerivedClass : BaseClass, TrembleClass {
        void HigherFunction (DerivedClass &) {
          cout << "I am hiiiiiiiiigh on emotion, hiiiiiiigh";
        }
    }

    {
      DerivedClass *derived_pointer = new DerivedClass;
      BaseClass *base_pointer = dynamic_cast<BaseClass*> derived_pointer;

      // This is wrong!
      TrembleClass *tremble_pointer = dynamic_cast<TrembleClass*> base_pointer;

      tremble_pointer->TrembleFunction();
    }

με object memory layout κάτι σαν αυτό:

    ADDRESS     +--------------------------------------+  <-- DerivedClass object
                |                                      |
    0x0800C000  |  DerivedClass::HigherFunction        |
                |  0x90 0x90 0x90                      |
                |                                      |
    0x0800F000  |  +-----------------------------------+  <-- BaseClass object
                |  |                                   |
                |  |  BaseClass::BaseFunction          |
                |  |  0x34 0x45 0x67 0x89 0x90 0x91    |
                |  |                                   |
                +  +-----------------------------------+
                |                                      |
    0x08012000  |  +-----------------------------------+  <-- TrembleClass object
                |  |                                   |
                |  |  TrembleClass::TrembleFunction    |
                |  |  0x89 0x90 0x91 0x92 0x93 0x94    |
                |  |                                   |
                +--+-----------------------------------+

Με αυτό το memory layout δεν υπάρχει 'valid' τρόπος να μετατρέψεις ένα
pointer σε DerivedClass πρώτα σε BaseClass και μετά _το αποτέλεσμα_ να
το μετατρέψεις πάλι σε TrembleClass.  Τα δυο παρακάτω μέρη κώδικα μπορεί
να έχουν νόημα και μπορεί να «μεταφραστούν» εσωτερικά στον κώδικα σε
σχόλια που ακολουθεί το κάθε snippet:

    // Valid example #1

    DerivedClass *derived_pointer = new DerivedClass;
    base_pointer = dynamic_cast<BaseClass *> derived_pointed;

    // Possible expansion of example #1
    //
    //   BaseClass *return_value = 0;    // return 0 if conversion fails
    //
    //   // Check if the class looks right, and point to the encapsulated
    //   // BaseClass object within DerivedClass.
    //   if (derived_pointer->getType() == DerivedClass) {
    //     return_value = ((unsigned char *)derived_pointer) + 0x3000;
    //   }
    //   return (BaseClass *)return_value;


    // Valid example #2

    DerivedClass *derived_pointer = new DerivedClass;
    tremble_pointer = dynamic_cast<TrembleClass *> derived_pointed;

    // Possible expansion of example #1
    //
    //   TrembleClass *return_value = 0;    // return 0 if conversion fails
    //
    //   // Check if the class looks right, and point to the encapsulated
    //   // TrembleClass object within DerivedClass.
    //   if (derived_pointer->getType() == DerivedClass) {
    //     return_value = ((unsigned char *)derived_pointer) + 0x6000;
    //   }
    //   return (TrembleClass *)return_value;


Πρόσεξε το «μαγικό» offset του κάθε encapsulated object (0x3000 για το
BaseClass, και 0x6000 για το TrembleClass).

Τώρα φαντάσου τι μπορεί να γίνει αν κάνεις πρώτα το ένα dynamic_cast και
μετά στο αποτέλεσμα το δεύτερο dynamic_cast ;-)

> Αυτό που θέλω είναι να μετατρέψω δείκτες Α* σε δείκτες Β*. Φαντάζομαι αυτό
> μπορεί να γίνει τόσο με dynamic_cast όσο και με reinterpret_cast. Με ποιό από
> τα δύο λοιπόν θα πρέπει να το κάνω, και γιατί?

Να μην το κάνεις.

Αν δύο classes δεν έχουν grand-parent, parent, κλπ. σχέση, το
dynamic_cast θα κάνει κάτι undefined και σίγουρα πολύ λάθος.

Ξανασκέψου γιατί θέλεις να μετατρέψεις ένα άσχετο class σε ένα άλλο,
εντελώς άσχετο, class.

Μήπως τελικά αυτό που θέλεις είναι απλά ένα mixin class που υλοποιεί
κάτι «κοινό» για τα class A και B;



More information about the Linux-greek-users mailing list