2014-10-15 14 views
13

Jest to w zasadzie kontynuacja an earlier question (nie jestem przeze mnie postawiony, ale jestem zainteresowany odpowiedzią).Wywołanie wirtualnej metody szablonu bazowego z wyprowadzonej szablonu szablonu warstwy zastępczej

Pytanie brzmi:Dlaczego kompilator/linker nie rozwiąże wywołanie funkcji wirtualnych z klasy pochodnej? W tym przypadku klasa pochodna jest klasą szablonów z parametrami variadic, która stosuje wielokrotne dziedziczenie względem tej samej klasy szablonów wiele razy (jeden raz dla każdego typu w parametrach variadic).

W konkretnym przykładzie poniżej klasa pochodna to JobPlant i jest wywoływana od Worker. Wywołanie abstrakcyjnej metody work() nie łączy się, wywołując linki workaround() i wykonuje w oczekiwany sposób.

Są awarie łącza, jak pokazano przez 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 do demonstracji obejście roboczego.

Job jest abstrakcyjną klasą podstawową i ma powiązane klasy pochodne. Work to klasa abstrakcyjnych szablonów, która wykonuje zadanie. Worker jest szablon, który identyfikuje JOB i wykonuje go (struct zamiast class wyłącznie w celu zmniejszenia bałaganu składni):

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

JobPlant jest klasa szablon parametryzowane przez JOBS, że znajdzie Worker wykonać job. JobPlant dziedziczy po Work dla każdego rodzaju pracy w JOBS. MyJobPlant jest instancją JobPlant i implementuje metody wirtualne work z powiązanych klas abstrakcyjnych Work.

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

Odpowiedz

9

jawnie wywołać p->Work<JOB>::work(), czyli czystego metoda wirtualna w Work<JOB>. Ta metoda nie jest zaimplementowana (w końcu jest czysta).

Nie ma znaczenia, że ​​klasy pochodne mogły zaimplementować/przesłonić tę metodę. Work<JOB>:: mówi, że chcesz wersję z tej klasy, a nie coś z klasy pochodnej. Brak dynamicznej wysyłki.

(Work<JOB>::work() jest składnia należałoby użyć, aby wywołać metodę klasy bazowej z metody nadrzędnego w klasie pochodnej, a tam na pewno chcesz metodę klasy bazowej.)


Po usunięciu następnie jawne Work<JOB>::, wynikiem jest błąd dwuznaczności. Podczas próby rozwiązania nazwy work, kompilator najpierw próbuje ustalić, która z klas bazowych zawiera tę nazwę. Następnie następnym krokiem byłaby rozdzielczość przeciążania pomiędzy różnymi metodami work w tej klasie.

Niestety, pierwszy krok prowadzi do niejednoznaczności: więcej niż jedna klasa bazowa zawiera nazwę work. Kompilator nigdy nie próbuje znaleźć odpowiedniego przeciążenia.(Nie są to przecież przeciążenia, ale sprzeczne ze sobą, ponieważ pochodzą z różnych klas).

Zwykle można to rozwiązać, przenosząc nazwy metod klasy podstawowej do klasy pochodnej z using (lub jakkolwiek technicznie nazywa się to, co robi using). Jeśli dodasz deklaracje using dla wszystkich metod bazowych, kompilator znajdzie nazwę work w klasie pochodnej (bez niejasności), a następnie może kontynuować rozwiązywanie przeciążenia (również nie jest to ambiwalentne).

Szablon variadyczny komplikuje rzeczy, ponieważ nie sądzę, że using Work<JOBS>::work...; jest legalny (a mój kompilator też tak nie uważa). Ale jeśli komponować poszczególne klasy bazowe „ręcznie”, wszystkie metody pracy mogą być doprowadzone do ostatecznej klasy:

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

Z tego konstruktu, problematyczny p->work(dynamic_cast<const JOB &>(job));compiles and runs successfully.

+0

Huh. To było proste. Czy możesz wyjaśnić, dlaczego wywołanie 'p-> pracy (dynamic_cast (zadanie)) powoduje błąd niejednoznaczności? Zobacz: http://ideone.com/2vL57z – jxh

+0

@jxh: Zobacz moją edycję :) – sth

+2

Zastąpienie "p-> Work :: work()" przez następujący kod rozwiązuje również problem. Praca & w = * p; w.work (dynamic_cast (zadanie)); – JVApen