2017-07-18 13 views
21

Rozważmy następujący standardowy przykład CRTP:Protect CRTP wzór ze stosu przepełnione w „czystej wirtualny” zwraca

#include <iostream> 

template<class Derived> 
struct Base { 
    void f() { static_cast<Derived *>(this)->f(); } 
    void g() { static_cast<Derived *>(this)->g(); } 
}; 

struct Foo : public Base<Foo> { 
    void f() { std::cout << 42 << std::endl; } 
}; 

int main() { 
    Foo foo; 
    foo.f(); // just OK 
    foo.g(); // this will stack overflow and segfault 
} 

Jeśli to był regularny wirtualnego dziedziczenie mogę mieć znak wirtualnych f i g metody jak czysta jak

struct Base { 
    virtual void f() = 0; 
    virtual void g() = 0; 
}; 

i uzyskaj komunikat o błędzie kompilacji o numerze Foo będącym abstraktem. Ale CRTP nie oferuje takiej ochrony. Czy mogę go jakoś wdrożyć? Sprawdzanie czasu pracy jest również akceptowalne. Pomyślałem o porównaniu wskaźnika this->f z static_cast<Derived *>(this)->f, ale nie udało się go uruchomić.

+2

Nie wiem, czy jest to zachowanie zdefiniowane w standardzie, ale można użyć 'static_assert' wewnątrz' Base :: g', którego 'i Derived :: g! = & Base :: g'. – Holt

+6

Wygląda na to, że działa również: http://coliru.stacked-crooked.com/a/80021ad0bfb7aa47 –

+0

@ JohannesSchaub-litb To jest sprytne! Powinieneś napisać odpowiedź. – Yakk

Odpowiedz

12

Oto inna możliwość:

#include <iostream> 

template<class Derived> 
struct Base { 
    auto f() { return static_cast<Derived *>(this)->f(); } 
    auto g() { return static_cast<Derived *>(this)->g(); } 
}; 

struct Foo : public Base<Foo> { 
    void f() { std::cout << 42 << std::endl; } 
}; 

int main() { 
    Foo foo; 
    foo.f(); // just OK 
    foo.g(); // this will not compile 
} 

Dla GCC, daje dość jasny komunikat o błędzie ("błąd: użycie" auto Base :: g() [z Derived = Foo] "przed odliczeniem" auto ""), podczas gdy dla Clang, to daje nieco mniej czytelną nieskończoną liczbę powtórzeń Wykonaj instancję szablonu Base<Foo>::g, z instancją g, która sama się tworzy, ale ostatecznie kończy się błędem.

+1

To jest rzeczywiście rzadkie, że klang daje gorszy komunikat o błędzie niż gcc, z mojego doświadczenia. – Yakk

+3

Najnowsza wersja clang [wydaje się produkować] (https://godbolt.org/g/ozbnVB) coś blisko gcc (clang> = 3.9.1). – Holt

+2

@ Holt Wszechświat został przywrócony na zamówienie. – Yakk

23

można dochodzić w czasie kompilacji, że oba wskaźniki do funkcji składowych są różne, np .:

template<class Derived> 
struct Base { 
    void g() { 
     static_assert(&Derived::g != &Base<Derived>::g, 
         "Derived classes must implement g()."); 
     static_cast<Derived *>(this)->g(); 
    } 
}; 
+2

W przypadku niepewności, czy było to standardowe zachowanie zdefiniowane w powyższym komentarzu. Chcesz rozwinąć to w tej odpowiedzi? – Yakk

+1

@Yakk Nie byłem pewien co do zasady (konwersja/porównanie) dla wskaźnika do funkcji składowych między klasą podstawową/pochodną. Sprawdziłem standard (w szczególności [conv.mem # 2]) i kilka pytań na temat SO i jestem przekonany, że jest to poprawne. Ale jeśli masz powody, by sądzić, że tak nie jest, możesz to wyjaśnić. Jeśli to będzie niewłaściwe, szybko usunę tę odpowiedź. – Holt

0

Można rozważyć robi coś takiego zamiast. Można dokonać Derived członka i albo dostarczyć go jako parametr szablonu bezpośrednio po każdym instancji Base albo użyć typu alias co mam zrobić w tym przykładzie:

template<class Derived> 
struct Base { 
    void f() { d.f(); } 
    void g() { d.g(); } 
private: 
    Derived d; 
}; 

struct FooImpl { 
    void f() { std::cout << 42 << std::endl; } 
}; 

using Foo = Base<FooImpl>; 

int main() { 
    Foo foo; 
    foo.f(); // OK 
    foo.g(); // compile time error 
} 

Oczywiście Derived nie jest już pochodzi, więc możesz wybrać lepszą nazwę dla niego.

+3

Nazwa nie jest tak zła jak zmieniona semantyka. Nie można już zastąpić 'Derived' dla' Base '. Tak więc funkcje ogólne a-la 'template void foo (Base &) {}' nie będzie już działać. – StoryTeller

+0

@StoryTeller Nie udaje, że jest semantycznie identyczny. Jest oznaczona jako możliwa (bezpieczna) alternatywa, która jest dobra w wielu sytuacjach.(Używam go cały czas) – Galik

12

Można korzystać z tego rozwiązania, można mieć funkcję czystego "non-virtual abstrakcyjne" i mapuje jak najwięcej do CRTP to recommendation of H. Sutter:

template<class Derived> 
struct Base 
    { 
    void f(){static_cast<Derived*>(this)->do_f();} 
    void g(){static_cast<Derived*>(this)->do_g();} 

    private: 
    //Derived must implement do_f 
    void do_f()=delete; 
    //do_g as a default implementation 
    void do_g(){} 
    }; 

struct derived 
    :Base<derived> 
    { 
    friend struct Base<derived>; 

    private: 
    void do_f(){} 
    };