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