Я не могу думать о правильном вопросе, чтобы описать проблему. Надеюсь, приведенные ниже детали объясняют мою проблему ясной.
Рассмотрим следующий код
#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();
}
Он будет компилироваться и выполняться, хотя определение D2
намеренно неверно. Первый вызов d2.call_impl()
выведет некоторые случайные биты, которые ожидаются, поскольку D2::data_
не был инициализирован. Второй и третий вызовы будут выводиться 100
для data_
.
Я понимаю, почему он будет компилироваться и запускаться, исправьте меня, если я ошибаюсь.
Когда мы вызываем вызов d2.call()
, вызов разрешается Base<D1>::call
, и он будет отбрасывать this
до D1
и вызывает D1::call_impl
. Поскольку D1
действительно имеет производную форму Base<D1>
, значит, отличное исполнение во время компиляции.
Во время выполнения после трансляции this
, хотя это действительно объект D2
обрабатывается так, как будто это D1
, а вызов D1::call_impl
изменяет биты памяти, которые должны be D1::data_
и выводить. В этом случае эти биты оказались там, где D2::data_
. Я думаю, что второй d2.call_impl()
также должен быть undefined в зависимости от реализации С++.
Дело в том, что этот код, хотя и неверно, не даст пользователю никаких признаков ошибки. Что я действительно делаю в своем проекте, так это то, что у меня есть базовый класс CRTP, который действует как механизм отправки. Другой класс в библиотеке обращается к интерфейсу базового класса CRTP, скажем call
и call
отправит на call_dispatch
, который может быть реализован по умолчанию для базового класса или реализации производного класса. Все они будут работать нормально, если определяемый пользователем производный класс, например D
, действительно получен из Base<D>
. Это вызовет ошибку времени компиляции, если она получена из Base<Unrelated>
, где Unrelated
не получен из Base<Unrelated>
. Но это не помешает пользователю писать код, как указано выше.
Пользователь использует библиотеку, исходя из базового класса CRTP и предоставляя некоторые детали реализации. Существуют, конечно, другие альтернативы дизайна, которые могут избежать проблемы неправильного использования, как указано выше (например, абстрактный базовый класс). Но позвольте отложить их на время и просто поверьте мне, что мне нужен этот дизайн по какой-то причине.
Итак, мой вопрос в том, что я могу предотвратить использование пользователем неправильного производного класса, как показано выше. То есть, если пользователь пишет производный класс реализации, скажем D
, но он вывел его из Base<OtherD>
, тогда возникает ошибка времени компиляции.
Одним из решений является использование dynamic_cast
. Тем не менее, это экспансивно и даже когда он работает, это ошибка времени выполнения.