Impedir que o usuário seja derivado de uma base incorreta de CRTP
Não consigo pensar em um título de pergunta adequado para descrever o problema. Espero que os detalhes abaixo expliquem claramente o meu problema.
Considere o seguinte código
#include <iostream>
template <typename Derived>
class Base
{
public :
void call ()
{
static_cast<Derived *>(this)->call_impl();
}
};
class D1 : public Base<D1>
{
public :
void call_impl ()
{
data_ = 100;
std::cout << data_ << std::endl;
}
private :
int data_;
};
class D2 : public Base<D1> // This is wrong by intension
{
public :
void call_impl ()
{
std::cout << data_ << std::endl;
}
private :
int data_;
};
int main ()
{
D2 d2;
d2.call_impl();
d2.call();
d2.call_impl();
}
Ele irá compilar e executar embora a definição deD2
está intencionalmente errado. A primeira chamadad2.call_impl()
irá produzir alguns bits aleatórios que é esperado comoD2::data_
não foi inicializado. A segunda e terceira chamadas serão todas100
para odata_
.
Eu entendo por que ele irá compilar e executar, me corrija se eu estiver errado.
Quando fazemos a ligaçãod2.call()
, a chamada é resolvida paraBase<D1>::call
, e isso vai lançarthis
paraD1
e ligueD1::call_impl
. PorqueD1
é de fato forma derivadaBase<D1>
, então o elenco está bem em tempo de compilação.
Em tempo de execução, após o elenco,this
, enquanto é verdadeiramente umD2
objeto é tratado como se fosseD1
e a chamada paraD1::call_impl
vai modificou os bits de memória que deveriam serD1::data_
e saída. Nesse caso, esses bits aconteceram ondeD2::data_
está. Eu acho que o segundod2.call_impl()
também deve ser um comportamento indefinido, dependendo da implementação do C ++.
O ponto é, este código, enquanto intensionalmente errado, não dará nenhum sinal de erro ao usuário. O que realmente estou fazendo no meu projeto é que eu tenho uma classe base CRTP que atua como um mecanismo de despacho. Outra classe na biblioteca acessa a interface da classe base CRTP, digamoscall
ecall
vai despachar paracall_dispatch
que pode ser implementação padrão de classe base ou implementação de classe derivada. Tudo isso funcionará bem se a classe derivada definida pelo usuário, digamosD
, é de fato derivado deBase<D>
. Ele aumentará o erro de tempo de compilação se for derivado deBase<Unrelated>
OndeUnrelated
não é derivado deBase<Unrelated>
. Mas isso não impedirá o código de gravação do usuário como acima.
O usuário usa a biblioteca derivando da classe CRTP de base e fornecendo alguns detalhes de implementação. Há certamente outras alternativas de design que podem evitar o problema do uso incorreto como acima (por exemplo, uma classe base abstrata). Mas vamos deixá-los de lado por enquanto e acredite em mim que preciso desse design por algum motivo.
Então, minha pergunta é que, existe alguma maneira que eu possa impedir o usuário de escrever classe derivada incorreta como ver acima. Ou seja, se o usuário escrever uma classe de implementação derivada, digaD
, mas ele derivou deBase<OtherD>
, então um erro de tempo de compilação será levantado.
Uma solução é usardynamic_cast
. No entanto, isso é expansivo e, mesmo quando funciona, é um erro de tempo de execução.