Domanda

Questo è stato chiesto durante l'intervista.

Come scrivere proprio dynamic_cast. Credo che, in base alla funzione di nome di typeid.

Ora come implementare propria typid? Non ho nessuna idea su di esso.

È stato utile?

Soluzione

C'è un motivo per cui non si dispone di alcun indizio, dynamic_cast e static_cast non sono come const_cast o reinterpret_cast, essi effettivamente svolgere l'aritmetica dei puntatori e sono un po 'typesafe.

L'aritmetica dei puntatori

Al fine di illustrare questo, pensare al seguente disegno:

struct Base1 { virtual ~Base1(); char a; };
struct Base2 { virtual ~Base2(); char b; };

struct Derived: Base1, Base2 {};

Un esempio di Derived dovrebbe apparire qualcosa di simile (si basa su GCC dal momento che è in realtà il compilatore dipendente ...):

|      Cell 1       | Cell 2 |      Cell 3        | Cell 4 |
| vtable pointer    |    a   | vtable pointer     |    b   |
|         Base 1             |        Base 2               |
|                     Derived                              |

Ora pensate del lavoro necessario per la fusione:

  • Casting da Derived a Base1 non richiede alcun lavoro supplementare, che sono allo stesso indirizzo fisico
  • trasmissione da Derived a risulta necessario per Base2 spostare il puntatore da 2 byte

Pertanto, è necessario conoscere il layout della memoria degli oggetti per poter fusione tra un oggetto derivato e una sua base. E questo è noto solo al compilatore, le informazioni non sono accessibili attraverso qualsiasi API, non è standardizzata o qualsiasi altra cosa.

In codice, questo si tradurrebbe come:

Derived derived;
Base2* b2 = reinterpret_cast<Base2>(((char*)&derived) + 2);

E questo è, ovviamente, per un static_cast.

Ora, se tu fossi in grado di utilizzare static_cast nella realizzazione di dynamic_cast, allora si potrebbe sfruttare il compilatore e lasciarlo gestire il puntatore aritmetico per voi ... ma non sei ancora fuori dal bosco.

dynamic_cast scrittura?

Per prima cosa, abbiamo bisogno di chiarire le specifiche dei dynamic_cast:

  • ritorna dynamic_cast<Derived*>(&base); null se base non è un'istanza di Derived.
  • dynamic_cast<Derived&>(base); getta std::bad_cast in questo caso.
  • dynamic_cast<void*>(base); restituisce l'indirizzo della classe più derivata
  • dynamic_cast rispettare le specifiche di accesso (public, protected e private eredità)

Non so voi, ma penso che sarà brutto. Utilizzando typeid non è sufficiente qui:

struct Base { virtual ~Base(); };
struct Intermediate: Base {};
struct Derived: Base {};

void func()
{
  Derived derived;
  Base& base = derived;
  Intermediate& inter = dynamic_cast<Intermediate&>(base); // arg
}

Il problema qui è che typeid(base) == typeid(Derived) != typeid(Intermediate), quindi non si può fare affidamento su che sia.

Un'altra cosa divertente:

struct Base { virtual ~Base(); };
struct Derived: virtual Base {};

void func(Base& base)
{
  Derived& derived = static_cast<Derived&>(base); // Fails
}

static_cast non funziona quando è coinvolto l'ereditarietà virtuale ... quindi abbiamo andare un problema di puntatore calcolo aritmetico strisciante.

Una soluzione quasi

class Object
{
public:
  Object(): mMostDerived(0) {}
  virtual ~Object() {}

  void* GetMostDerived() const { return mMostDerived; }

  template <class T>
  T* dynamiccast()
  {
    Object const& me = *this;
    return const_cast<T*>(me.dynamiccast());
  }

  template <class T>
  T const* dynamiccast() const
  {
    char const* name = typeid(T).name();
    derived_t::const_iterator it = mDeriveds.find(name);
    if (it == mDeriveds.end()) { return 0; }
    else { return reinterpret_cast<T const*>(it->second); }
  }

protected:
  template <class T>
  void add(T* t)
  {
    void* address = t;
    mDerived[typeid(t).name()] = address;
    if (mMostDerived == 0 || mMostDerived > address) { mMostDerived= address; }
  }

private:
  typedef std::map < char const*, void* > derived_t;
  void* mMostDerived;
  derived_t mDeriveds;
};

// Purposely no doing anything to help swapping...

template <class T>
T* dynamiccast(Object* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T const* dynamiccast(Object const* o) { return o ? o->dynamiccast<T>() : 0; }

template <class T>
T& dynamiccast(Object& o)
{
  if (T* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

template <class T>
T const& dynamiccast(Object const& o)
{
  if (T const* t = o.dynamiccast<T>()) { return t; }
  else { throw std::bad_cast(); }
}

È necessario alcune piccole cose nel costruttore:

class Base: public Object
{
public:
  Base() { this->add(this); }
};

Quindi, cerchiamo di controllo:

  • usi classici: ok
  • eredità virtual? dovrebbe funzionare ... ma non testato
  • rispettando specificatori di accesso ... ARG: /

In bocca al lupo a tutti coloro che cercando di attuare questo al di fuori del compilatore, in realtà: x

Altri suggerimenti

Un modo è quello dichiarare un identificatore statica (un numero intero per esempio) che definisce l'ID classe. Nella classe è possibile implementare sia statici e le routine con ambito wich restituisce l'identificatore di classe ( Ricordate di routine marchio virtuale ).

L'identificatore statica deve essere inizializzato in fase di inizializzazione dell'applicazione. Un modo è quello di chiamare una routine InitializeId per ogni classe, ma questo significa che i nomi delle classi devono essere conosciuti, e il codice di inizializzazione deve essere modificato ogni volta che la gerarchia delle classi viene modificato. Un altro modo è quello di controllare identificatore valido in fase di costruzione, ma ciò introduce, viene eseguito un overhead in quanto ogni volta che una classe è costruito l'assegno, ma solo la prima volta è utile (inoltre se nessuna classe è costruito, la routine statico non può essere utile dal momento che l'identificatore non è mai inizializzato).

Un un'applicazione equa potrebbe essere una classe template:

template <typename T>
class ClassId<T>
{
    public:

    static int GetClassId() { return (sClassId); }

    virtual int GetClassId() const { return (sClassId); }

    template<typename U> static void StateDerivation() {
        gClassMap[ClassId<T>::GetClassId()].push_back(ClassId<U>::GetClassId());
    }

    template<typename U> const U DynamicCast() const {
        std::map<int, std::list<int>>::const_iterator it = gClassMap.find(ClassId<T>); // Base class type, with relative derivations declared with StateDerivation()
        int id = ClassId<U>::GetClassId();

        if (id == ClassId<T>::GetClassId()) return (static_cast<U>(this));

        while (it != gClassMap.end()) {
            for (std::list<int>::const_iterator = pit->second.begin(), pite = it->second->end(); pit != pite; pit++) {
                if ((*pit) == id) return (static_cast<U>(this));
                // ... For each derived element, iterate over the stated derivations.
                // Easy to implement with a recursive function, better if using a std::stack to avoid recursion.
            }
        }

        return (null); 
    }  

    private:

    static int sClassId;
}

#define CLASS_IMP(klass) static int ClassId<klass>::sClassId = gClassId++;

// Global scope variables    

static int gClassId = 0;
static std::map<int, std::list<int>> gClassMap;

CLASS_IMP è definita per ciascuna classe derivanti da ClassId e gClassId e gClassMap è visibile in ambito globale.

Gli identificatori di classe disponibili tutt'oggi conservati da un unico statico variabile intera accessibili da tutte le classi (una classe base ClassID o una variabile globale), che viene incrementato ogni volta che viene assegnato un nuovo identificatore di classe.

Per rappresenta la gerarchia delle classi è una mappa sufficiente tra l'identificatore di classe e le sue classi derivate. Per sapere se ogni classe può essere colato a una classe specifica, iterare sulla mappa e di controllo derivazioni di dichiarazione.

Ci sono molte difficoltà a faccia ... uso di riferimenti! derivazioni virtuale! Bad casting! Bad tipo di classe di inizializzazione mappatura porterà ad errori di colata ...


I rapporti tra le classi sono definite manualmente, hardcoded con la routine di inizializzazione. Questo permette di determinare se una classe derivata da, o se due classi come una derivazione comune.

class Base : ClassId<Base> {  }
#define CLASS_IMP(Base);
class Derived : public Base, public ClassId<Derived> {  }
#define CLASS_IMP(Derived);
class DerivedDerived : public Derived, public ClassId<DerivedDerived> {  }
#define CLASS_IMP(DerivedDerived);

static void DeclareDerivations()
{
    ClassId<Base>::StateDerivation<Derived>();
    ClassId<Derived>::StateDerivation<DerivedDerived>();
}

Personalmente sono d'accordo con "un motivo ci sarà se compilatori implementa dynamic_cast"; Probabilmente il compilatore fare le cose meglio (in particolare per quanto riguarda il codice di esempio!).

Per tentare una risposta un po 'meno di routine, se un po' meno definito:

Quello che dovete fare è lanciare il puntatore a un int *, creare un nuovo tipo T sullo stack, gettato un puntatore ad esso a int *, e confrontare il primo int in entrambi i tipi. Questo farà un confronto indirizzo vtable. Se sono dello stesso tipo, avranno la stessa vtable. Altrimenti, non lo fanno.

Il più sensibile di noi solo bastone una costante integrale nelle nostre classi.

Facile. Trarre tutti gli oggetti da talune interfacce typeid con una funzione WhoAmI virtuale (). Override in tutte le classi derivate.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top