2011-01-14 13 views
12

Ho un codice legacy che, invece di funzioni virtuali, utilizza un campo kind per eseguire l'invio dinamico. Sembra qualcosa di simile:Invio dinamico C++ senza funzioni virtuali

// Base struct shared by all subtypes 
// Plain-old data; can't use virtual functions 
struct POD 
{ 
    int kind; 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 
}; 

enum Kind { Kind_Derived1, Kind_Derived2, Kind_Derived3 /* , ... */ }; 

struct Derived1: POD 
{ 
    Derived1(): kind(Kind_Derived1) {} 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 

    // ... plus other type-specific data and function members ... 
}; 

struct Derived2: POD 
{ 
    Derived2(): kind(Kind_Derived2) {} 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 

    // ... plus other type-specific data and function members ... 
}; 

struct Derived3: POD 
{ 
    Derived3(): kind(Kind_Derived3) {} 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
    int GetXyzzy(); 

    // ... plus other type-specific data and function members ... 
}; 

// ... and so on for other derived classes ... 

e poi i membri di funzione della classe POD sono implementati in questo modo:

int POD::GetFoo() 
{ 
    // Call kind-specific function 
    switch (kind) 
    { 
    case Kind_Derived1: 
     { 
     Derived1 *pDerived1 = static_cast<Derived1*>(this); 
     return pDerived1->GetFoo(); 
     } 
    case Kind_Derived2: 
     { 
     Derived2 *pDerived2 = static_cast<Derived2*>(this); 
     return pDerived2->GetFoo(); 
     } 
    case Kind_Derived3: 
     { 
     Derived3 *pDerived3 = static_cast<Derived3*>(this); 
     return pDerived3->GetFoo(); 
     } 

    // ... and so on for other derived classes ... 

    default: 
     throw UnknownKindException(kind, "GetFoo"); 
    } 
} 

POD::GetBar(), POD::GetBaz(), POD::GetXyzzy(), e gli altri membri sono implementate in modo simile.

Questo esempio è semplificato. Il codice attuale ha una dozzina di sottotipi diversi di POD e un paio di dozzine di metodi. Nuovi sottotipi di e nuovi metodi vengono aggiunti abbastanza frequentemente, quindi ogni volta che lo facciamo, dobbiamo aggiornare tutte queste dichiarazioni switch.

Il modo tipico per gestirlo sarebbe quello di dichiarare i membri della funzione virtual nella classe POD, ma non possiamo farlo perché gli oggetti si trovano nella memoria condivisa. C'è un sacco di codice che dipende dal fatto che queste strutture siano semplici vecchi dati, quindi anche se potessi immaginare un modo per avere funzioni virtuali in oggetti a memoria condivisa, non vorrei farlo.

Quindi, sto cercando suggerimenti sul modo migliore per pulire questo in modo che tutta la conoscenza di come chiamare i metodi di sottotipo sia centralizzata in un posto, piuttosto che sparsi in una dozzina di dozzine di switch affermazioni in un una dozzina di funzioni.

Quello che succede a me è che posso creare una sorta di classe adattatore che avvolge un POD e utilizza modelli per ridurre al minimo la ridondanza. Ma prima di iniziare questo percorso, mi piacerebbe sapere come gli altri hanno affrontato questo.

+0

Lei ha detto che c'era un sacco di codice a seconda di questa classe. Puoi aggiungere dei campi o la struttura deve rimanere uguale? –

+0

La struttura dovrebbe rimanere sostanzialmente la stessa. Abbiamo un sacco di enormi array di queste cose nella memoria condivisa, e stanno già andando a sbattere contro i limiti delle dimensioni della memoria. –

+0

tutti i processi multipli hanno la stessa versione della libreria o no? –

risposta

11

È possibile utilizzare una tabella di salto. Questo è ciò che la maggior parte dei dispacci virtuali sembra essere sotto il cofano, e PUO 'costruirlo manualmente.

template<typename T> int get_derived_foo(POD*ptr) { 
    return static_cast<T>(ptr)->GetFoo(); 
} 
int (*)(POD*) funcs[] = { 
    get_derived_foo<Derived1>, 
    get_derived_foo<Derived2>, 
    get_derived_foo<Derived3> 
}; 
int POD::GetFoo() { 
    return funcs[kind](this); 
} 

Per un breve esempio.

Quali sono esattamente i limiti dell'essere nella memoria condivisa? Mi sono reso conto che non ne so abbastanza qui. Significa che non posso usare i puntatori, perché qualcuno in un altro processo cercherà di usare quei puntatori?

È possibile utilizzare una mappa di stringhe, in cui ogni processo ottiene la propria copia della mappa. Dovresti passare questo a GetFoo() in modo che possa trovarlo.

struct POD { 
    int GetFoo(std::map<int, std::function<int()>& ref) { 
     return ref[kind](); 
    } 
}; 

Modifica: Naturalmente, non è necessario utilizzare una stringa qui, è possibile utilizzare un int. L'ho usato solo come esempio. Dovrei cambiarlo di nuovo. Infatti, questa soluzione è piuttosto flessibile, ma la cosa importante è fare una copia dei dati specifici del processo, ad es. puntatori di funzioni o altro, quindi inoltratelo.

+0

Gli oggetti nella memoria condivisa vengono utilizzati da più processi. Un puntatore a funzione virtuale non sarà valido per tutti i processi. –

+2

Non ho esaminato i dettagli, ma in generale, la tabella fa riferimento a un puntatore in ogni oggetto. Se un thread ha il vtable per il tipo X all'indirizzo Y, esso memorizzerà Y nel campo vptr dell'oggetto.Non è garantito che anche se il vtable è memorizzato nello stesso identico indirizzo hardware nel sistema, i due diversi processi non lo vedranno in indirizzi logici diversi. Se questo è il caso, l'altro thread proverà ad usare il vtable nell'indirizzo sbagliato e muore. –

+1

Bello, ma sarebbe bello assicurarsi di generare correttamente gli array. Una macro del preprocessore potrebbe farlo. –

0

Ecco un esempio che utilizza il modello di modello Ricco ricorrente. Questo può soddisfare le tue esigenze se conosci più informazioni al momento della compilazione.

template<class DerivedType> 
struct POD 
{ 
    int GetFoo() 
    { 
     return static_cast<DerivedType*>(this)->GetFoo(); 
    } 
    int GetBar() 
    { 
     return static_cast<DerivedType*>(this).GetBar(); 
    } 
    int GetBaz() 
    { 
     return static_cast<DerivedType*>(this).GetBaz(); 
    } 
    int GetXyzzy() 
    { 
     return static_cast<DerivedType*>(this).GetXyzzy(); 
    } 
}; 

struct Derived1 : public POD<Derived1> 
{ 
    int GetFoo() 
    { 
     return 1; 
    } 
    //define all implementations 
}; 

struct Derived2 : public POD<Derived2> 
{ 
    //define all implementations 

}; 

int main() 
{ 
    Derived1 d1; 
    cout << d1.GetFoo() << endl; 
    POD<Derived1> *p = new Derived1; 
    cout << p->GetFoo() << endl; 
    return 0; 
} 
+0

Ma cosa faccio se inizio con un 'POD *', che in realtà punta a un sottotipo e voglio chiamare 'GetFoo()'? –

1

Ecco un approccio che utilizza metodi virtuali per implementare il tavolo salto, senza richiedere la classe Pod o le classi derivate di avere effettivamente funzioni virtuali.

L'obiettivo è semplificare l'aggiunta e la rimozione di metodi su più classi.

Per aggiungere un metodo, è necessario aggiungerlo a Pod utilizzando un modello chiaro e comune, è necessario aggiungere una funzione virtuale pura a PodInterface e una funzione di inoltro deve essere aggiunta ai PodFunc utilizzando un modello chiaro e comune.

Le classi derivate devono avere solo un oggetto di inizializzazione statica di file per impostare le cose, altrimenti sembrano proprio come fanno già.

// Pod header 

#include <boost/shared_ptr.hpp> 
enum Kind { Kind_Derived1, Kind_Derived2, Kind_Derived3 /* , ... */ }; 

struct Pod 
{ 
    int kind; 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 
}; 

struct PodInterface 
{ 
    virtual ~PodInterface(); 

    virtual int GetFoo(Pod* p) const = 0; 
    virtual int GetBar(Pod* p) const = 0; 
    virtual int GetBaz(Pod* p) const = 0; 

    static void 
    do_init(
      boost::shared_ptr<PodInterface const> const& p, 
      int kind); 
}; 

template<class T> struct PodFuncs : public PodInterface 
{ 
    struct Init 
    { 
     Init(int kind) 
     { 
      boost::shared_ptr<PodInterface> t(new PodFuncs); 
      PodInterface::do_init(t, kind); 
     } 
    }; 

    ~PodFuncs() { } 

    int GetFoo(Pod* p) const { return static_cast<T*>(p)->GetFoo(); } 
    int GetBar(Pod* p) const { return static_cast<T*>(p)->GetBar(); } 
    int GetBaz(Pod* p) const { return static_cast<T*>(p)->GetBaz(); } 
}; 


// 
// Pod Implementation 
// 

#include <map> 

typedef std::map<int, boost::shared_ptr<PodInterface const> > FuncMap; 

static FuncMap& get_funcmap() 
{ 
    // Replace with other approach for static initialisation order as appropriate. 
    static FuncMap s_funcmap; 
    return s_funcmap; 
} 

// 
// struct Pod methods 
// 

int Pod::GetFoo() 
{ 
    return get_funcmap()[kind]->GetFoo(this); 
} 

// 
// struct PodInterface methods, in same file as s_funcs 
// 

PodInterface::~PodInterface() 
{ 
} 

void 
PodInterface::do_init(
     boost::shared_ptr<PodInterface const> const& p, 
     int kind) 
{ 
    // Could do checking for duplicates here. 
    get_funcmap()[kind] = p; 
} 

// 
// Derived1 
// 

struct Derived1 : Pod 
{ 
    Derived1() { kind = Kind_Derived1; } 

    int GetFoo(); 
    int GetBar(); 
    int GetBaz(); 

    // Whatever else. 
}; 

// 
// Derived1 implementation 
// 

static const PodFuncs<Derived1>::Init s_interface_init(Kind_Derived1); 

int Derived1::GetFoo() { /* Implement */ } 
int Derived1::GetBar() { /* Implement */ } 
int Derived1::GetBaz() { /* Implement */ } 
1

Ecco il percorso di metaprogrammazione del modello che sto andando giù ora. Ecco quello che mi piace a questo proposito:

  • aggiunta del supporto per un nuovo tipo solo richiede l'aggiornamento LAST_KIND e l'aggiunta di un nuovo KindTraits.
  • C'è uno schema semplice per aggiungere una nuova funzione.
  • Le funzioni possono essere specializzate per tipi particolari se necessario.
  • Posso aspettarmi errori e avvisi in fase di compilazione, piuttosto che un misterioso comportamento scorretto in fase di esecuzione, se avessi svitato qualcosa.

Ci sono un paio di problemi:

attuazione
  • s' POD è ora dipende dalle interfacce di tutte le classi derivate. (Questo è già vero nell'implementazione esistente, quindi non sono preoccupato, ma è un po 'un odore.)
  • Sto contando sul compilatore per essere abbastanza intelligente da generare codice che è grosso modo equivalente al codice basato su switch.
  • Molti programmatori C++ si grattano la testa vedendo questo.

Ecco il codice:

// Declare first and last kinds 
const int FIRST_KIND = Kind_Derived1; 
const int LAST_KIND = Kind_Derived3; 

// Provide a compile-time mapping from a kind code to a subtype 
template <int KIND> 
struct KindTraits 
{ 
    typedef void Subtype; 
}; 
template <> KindTraits<Kind_Derived1> { typedef Derived1 Subtype; }; 
template <> KindTraits<Kind_Derived2> { typedef Derived2 Subtype; }; 
template <> KindTraits<Kind_Derived3> { typedef Derived3 Subtype; }; 

// If kind matches, then do the appropriate typecast and return result; 
// otherwise, try the next kind. 
template <int KIND> 
int GetFooForKind(POD *pod) 
{ 
    if (pod->kind == KIND) 
     return static_cast<KindTraits<KIND>::Subtype>(pod)->GetFoo(); 
    else 
     return GetFooForKind<KIND + 1>(); // try the next kind 
} 

// Specialization for LAST_KIND+1 
template <> int GetFooForKind<LAST_KIND + 1>(POD *pod) 
{ 
    // kind didn't match anything in FIRST_KIND..LAST_KIND 
    throw UnknownKindException(kind, "GetFoo"); 
} 

// Now POD's function members can be implemented like this: 

int POD::GetFoo() 
{ 
    return GetFooForKind<FIRST_KIND>(this); 
} 
+1

Hai perso l'opportunità di lanciare UnkindException! Questo approccio richiede che l'implementazione del POD abbia visto la definizione di ogni tipo derivato. Dato lo stato attuale del codice, potrebbe non interessarti, ma il requisito è lì. – janm

+0

Ho aggiunto la nota sulla dipendenza alla mia sezione "Preoccupazioni". –

0

Ampliando la soluzione si è conclusa con la seguente risolve la mappatura alle funzioni derivate in fase di inizializzazione del programma:

#include <typeinfo> 
#include <iostream> 
#include <functional> 
#include <vector> 

enum Kind 
{ 
    Kind_First, 
    Kind_Derived1 = Kind_First, 
    Kind_Derived2, 
    Kind_Total 
}; 

struct POD 
{ 
    size_t kind; 

    int GetFoo(); 
    int GetBar(); 
}; 

struct VTable 
{ 
    std::function<int(POD*)> GetFoo; 
    std::function<int(POD*)> GetBar; 
}; 

template<int KIND> 
struct KindTraits 
{ 
    typedef POD KindType; 
}; 

template<int KIND> 
void InitRegistry(std::vector<VTable> &t) 
{ 
    typedef typename KindTraits<KIND>::KindType KindType; 

    size_t i = KIND; 
    t[i].GetFoo = [](POD *p) -> int { 
     return static_cast<KindType*>(p)->GetFoo(); 
    }; 
    t[i].GetBar = [](POD *p) -> int { 
     return static_cast<KindType*>(p)->GetBar(); 
    }; 

    InitRegistry<KIND+1>(t); 
} 
template<> 
void InitRegistry<Kind_Total>(std::vector<VTable> &t) 
{ 
} 

struct Registry 
{ 
    std::vector<VTable> table; 

    Registry() 
    { 
     table.resize(Kind_Total); 
     InitRegistry<Kind_First>(table); 
    } 
}; 

Registry reg; 

int POD::GetFoo() { return reg.table[kind].GetFoo(this); } 
int POD::GetBar() { return reg.table[kind].GetBar(this); } 

struct Derived1 : POD 
{ 
    Derived1() { kind = Kind_Derived1; } 

    int GetFoo() { return 0; } 
    int GetBar() { return 1; } 
}; 
template<> struct KindTraits<Kind_Derived1> { typedef Derived1 KindType; }; 

struct Derived2 : POD 
{ 
    Derived2() { kind = Kind_Derived2; } 

    int GetFoo() { return 2; } 
    int GetBar() { return 3; } 
}; 
template<> struct KindTraits<Kind_Derived2> { typedef Derived2 KindType; }; 

int main() 
{ 
    Derived1 d1; 
    Derived2 d2; 
    POD *p; 

    p = static_cast<POD*>(&d1); 
    std::cout << p->GetFoo() << '\n'; 
    p = static_cast<POD*>(&d2); 
    std::cout << p->GetBar() << '\n'; 
} 
Problemi correlati