c++ - Proteja el patrón CRTP de la pila que se desborda en llamadas "puramente virtuales"
pure-virtual virtual-method (4)
Aquí hay otra posibilidad:
#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
}
Para GCC, da un mensaje de error bastante claro ( "error: uso de ''auto Base :: g () [con Derivado = Foo]'' antes de la deducción de ''auto''" ), mientras que para Clang, da un poco menos legible La creación de instancias de plantilla de Base<Foo>::g
repite de forma infinita, con g
crea una instancia de sí misma pero finalmente termina en un error.
Considere el siguiente ejemplo de CRTP estándar:
#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 se tratara de una herencia virtual normal, podría haber marcado los métodos f
y g
virtuales como puros como
struct Base {
virtual void f() = 0;
virtual void g() = 0;
};
y obtén un error de compilación acerca de que Foo
es abstracto. Pero CRTP no ofrece tal protección. ¿Puedo implementarlo de alguna manera? El control de tiempo de ejecución también es aceptable. Pensé en comparar este puntero - this->f
con static_cast<Derived *>(this)->f
, pero no conseguí que funcionara.
Podría usar esta solución, puede tener una función pura "abstracta no virtual", y mapea tanto como sea posible a CRTP esta recomendación 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(){}
};
Podrías considerar hacer algo como esto en su lugar. Puede hacer que Derived
un miembro y proporcionarlo como un parámetro de plantilla directamente cada vez que cree una Base
o, si no, usar un alias de tipo como lo he hecho en este ejemplo:
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
}
Por supuesto, Derived
ya no se deriva, así que puedes elegir un nombre mejor.
Puede afirmar en el momento de la compilación que los dos punteros a las funciones miembro son diferentes, por ejemplo:
template<class Derived>
struct Base {
void g() {
static_assert(&Derived::g != &Base<Derived>::g,
"Derived classes must implement g().");
static_cast<Derived *>(this)->g();
}
};