2014-10-15 11 views
13

Questo è essenzialmente un follow-up a an earlier question (non posta da me, ma io sono interessato a una risposta).chiamata metodo virtuale di modello di base dalla classe derivata template variadic

La domanda è:Perché il compilatore/linker non riescono a risolvere la chiamata alla funzione virtuale dalla classe derivata? In questo caso, la classe derivata è una classe template con parametri variadic che applica più ereditarietà rispetto alla stessa classe template più volte (una volta per ciascun tipo nei parametri variadic).

Nell'esempio concreto di seguito, la classe derivata è JobPlant e viene chiamata da Worker. Il richiamo del metodo astratto work() non riesce a collegarsi, mentre si invoca i collegamenti workaround() ed è eseguito nel modo previsto.

Questi sono i problemi nei collegamenti come mostrato da ideone:

/home/g6xLmI/ccpFAanK.o: In function `main': 
prog.cpp:(.text.startup+0x8e): undefined reference to `Work<JobA>::work(JobA const&)' 
prog.cpp:(.text.startup+0xc9): undefined reference to `Work<JobB>::work(JobB const&)' 
prog.cpp:(.text.startup+0xff): undefined reference to `Work<JobC>::work(JobC const&)' 
collect2: error: ld returned 1 exit status 

Follow this link per la dimostrazione della soluzione di lavoro.

Job è una classe base astratta, ed è associato classi derivate. Work è una classe template astratta che esegue un lavoro. Worker è un modello che identifica il JOB e compie (struct anziché class puramente per ridurre l'ingombro sintassi):

struct Job { virtual ~Job() {} }; 

struct JobA : Job {}; 
struct JobB : Job {}; 
struct JobC : Job {}; 

template <typename JOB> 
struct Work { 
    virtual ~Work() {} 
    virtual void work(const JOB &) = 0; 
    void workaround(const Job &job) { work(dynamic_cast<const JOB &>(job)); } 
}; 

template <typename PLANT, typename... JOBS> struct Worker; 

template <typename PLANT, typename JOB, typename... JOBS> 
struct Worker<PLANT, JOB, JOBS...> { 
    bool operator()(PLANT *p, const Job &job) const { 
     if (Worker<PLANT, JOB>()(p, job)) return true; 
     return Worker<PLANT, JOBS...>()(p, job); 
    } 
}; 

template <typename PLANT, typename JOB> 
struct Worker<PLANT, JOB> { 
    bool operator()(PLANT *p, const Job &job) const { 
     if (dynamic_cast<const JOB *>(&job)) { 
      p->Work<JOB>::work(dynamic_cast<const JOB &>(job)); 
      //p->Work<JOB>::workaround(job); 
      return true; 
     } 
     return false; 
    } 
}; 

Un JobPlant è una classe modello parametrizzato da JOBS, che trova un Worker per eseguire una job. Lo JobPlant eredita da Work per ogni tipo di lavoro in JOBS. MyJobPlant è un'istanza di JobPlant e implementa i metodi virtuali work dalle classi associate Work associate.

template <typename... JOBS> 
struct JobPlant : Work<JOBS>... { 
    typedef Worker<JobPlant, JOBS...> WORKER; 
    bool worker(const Job &job) { return WORKER()(this, job); } 
}; 

struct MyJobPlant : JobPlant<JobA, JobB, JobC> { 
    void work(const JobA &) { std::cout << "Job A." << std::endl; } 
    void work(const JobB &) { std::cout << "Job B." << std::endl; } 
    void work(const JobC &) { std::cout << "Job C." << std::endl; } 
}; 

int main() { 
    JobB j; 
    MyJobPlant().worker(j); 
} 

risposta

9

È esplicitamente chiama p->Work<JOB>::work(), cioè il metodo virtuale puro Work<JOB>. Questo metodo non è implementato (dopo tutto è puro).

Non importa che le classi derivate potrebbero hanno implementato/ignorato quel metodo. Il Work<JOB>:: dice che si desidera la versione da quella classe, non qualcosa da una classe derivata. Non si verifica alcuna spedizione dinamica.

(Work<JOB>::work() è la sintassi si usa per chiamare un metodo della classe base da un metodo imperativo di una classe derivata, e non ci si vuole veramente il metodo della classe base.)


Quando si rimuove poi esplicito Work<JOB>::, il risultato è un errore di ambiguità. Quando si tenta di risolvere il nome work, il compilatore prima tenta di determinare quale delle classi base contiene quel nome. Successivamente, il passaggio successivo sarà la risoluzione del sovraccarico tra i diversi metodi work in quella classe.

Ancora i primi risultati di step in ambiguità: più di una classe di base contiene il nome work. Il compilatore non cerca mai di capire il sovraccarico corrispondente.(Non sono davvero sovraccarichi ma in conflitto tra loro, poiché appartengono a classi diverse).

Di solito questo può essere risolto portando i nomi dei metodi della classe base nella classe derivata con using (o comunque tecnicamente si chiama cosa fa using). Se si aggiungono le dichiarazioni using per tutti i metodi work delle classi base, il compilatore trova il nome work nella classe derivata (nessuna ambiguità) e può quindi procedere con la risoluzione di sovraccarico (anche non ambigua).

Il modello variadic complica le cose poiché non credo che using Work<JOBS>::work...; sia legale (e anche il mio compilatore non la pensa così). Ma se si compone le classi base "manualmente", tutti i metodi di lavoro possono essere portati in classe finale:

template <typename JOB, typename... JOBS> 
struct CollectWork : Work<JOB>, CollectWork<JOBS...> { 
    using Work<JOB>::work; 
    using CollectWork<JOBS...>::work; 
}; 

template <typename JOB> 
struct CollectWork<JOB> : Work<JOB> { 
    using Work<JOB>::work; 
}; 

template<typename... JOBS> 
struct JobPlant : CollectWork<JOBS...> {           
    using CollectWork<JOBS...>::work; 
    typedef Worker<JobPlant, JOBS...> WORKER; 
    bool worker(const Job &job) { return WORKER()(this, job); } 
}; 

Con questo costrutto, la problematica p->work(dynamic_cast<const JOB &>(job));compiles and runs successfully.

+0

Huh. E 'stato semplice Puoi spiegare perché chiamare 'p-> work (dynamic_cast (job))' dà un errore di ambiguità? Vedere: http://ideone.com/2vL57z – jxh

+0

@jxh: vedere la mia modifica :) – sth

+2

Sostituire la 'p-> Work :: work()' seguendo il codice risolve anche il problema. Lavoro & w = * p; w.work (dynamic_cast (lavoro)); – JVApen