Prenons l'exemple de CRTP standard suivant:
#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
}
Si c’était un héritage virtuel normal, j’aurais pu marquer les méthodes virtuelles f
et g
comme pures, comme
struct Base {
virtual void f() = 0;
virtual void g() = 0;
};
et obtenez une erreur de compilation indiquant que Foo
est abstrait. Mais le CRTP n'offre pas une telle protection. Puis-je l'implémenter d'une manière ou d'une autre? La vérification à l'exécution est également acceptable. J'ai pensé à comparer le pointeur this->f
avec static_cast<Derived *>(this)->f
, mais je n'ai pas réussi à le faire fonctionner.
Voici une autre possibilité:
#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
}
Pour GCC, cela donne un message d'erreur assez clair ( "erreur: utilisation de 'auto Base :: g () [avec Derived = Foo]' avant déduction de 'auto'" ) il donne une instanciation de modèle légèrement moins lisible de Base<Foo>::g
, avec g
s'instanciant elle-même mais se terminant finalement par une erreur.
Vous pouvez affirmer au moment de la compilation que les deux pointeurs vers les fonctions membres sont différents, par exemple:
template<class Derived>
struct Base {
void g() {
static_assert(&Derived::g != &Base<Derived>::g,
"Derived classes must implement g().");
static_cast<Derived *>(this)->g();
}
};
Vous pouvez utiliser cette solution, vous pouvez avoir une fonction "abstraite non virtuelle" pure et mapper autant que possible au CRTP cette recommandation de 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(){}
};
Vous pourriez envisager de faire quelque chose comme ça à la place. Vous pouvez faire de Derived
un membre et le fournir directement en tant que paramètre de modèle chaque fois que vous instanciez une Base
ou utilisez un type alias comme je l'ai fait dans cet exemple:
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
}
Bien entendu, Derived
n'est plus dérivé _ donc vous pouvez en choisir un meilleur nom.