при запуске следующего кода в MSVC 2013, x64 Debug config, при выходе из функции main () будет отображаться окно сообщения с этим известным сообщением об ошибке
"Run-Time Check Failure #2 - Stack around the variable 'tmp' was corrupted.".
Вопрос, на который я не могу ответить: почему?
обратите внимание, что при работе в Release config сообщение об ошибке не появляется. (Зачем?)
отказ от ответственности: это просто пример кода, означающий, что я пытаюсь использовать этот же дизайн для других классов (одного базового и нескольких производных) с гораздо большим количеством методов и аргументов шаблона и с гораздо более сложным типом данных, чем базовый тип int *.
#include <iostream>
template <class T>
class base {
public:
base() {
static_cast<T*>(this)->setData();
}
~base() {
static_cast<T*>(this)->destroyData();
}
void print() {
static_cast<T*>(this)->print_int();
}
};
class derived : public base<derived> {
public:
void setData() {
x = new int();
}
void destroyData() {
delete x;
x = nullptr;
}
void print_int() {
std::cout << "x = " << *x << std::endl;
}
private:
derived() {}
derived(const derived& other) {}
inline derived& operator= (derived copy) {}
int *x;
};
int main() {
base<derived> tmp;
tmp.print();
return 0;
}
РЕДАКТИРОВАТЬ:
@ Якк, если я правильно понимаю, вы предлагаете в качестве решения этот код:
#include <iostream>
template <class T>
class mix_in : public T
{
public:
mix_in() { (this)->setData(); }
~mix_in() { (this)->destroyData(); }
void print() { (this)->print_int(); }
};
class foo
{
public:
void setData()
{
x = new int();
}
void destroyData()
{
delete x;
x = nullptr;
}
void print_int()
{
std::cout << "x = " << *x << std::endl;
}
foo() {}
private:
int *x;
};
int main()
{
mix_in<foo> tmp;
tmp.print();
return 0;
}
Работает просто отлично, спасибо. Я, вероятно, буду использовать этот шаблон, так как кажется, что нет решения использовать CRTP для того, что я пытаюсь сделать.
Но я все еще хотел бы понять, почему происходит повреждение стека. Несмотря на все дискуссии об использовании или неиспользовании CRTP для всех вещей, я хотел бы очень точно понять, почему это происходит.
Еще раз спасибо
tmp
это base<derived>
не derived
, Смысл CRTP в том, что базовый класс «знает» фактический тип объекта, потому что он передается в качестве параметра шаблона, но если вы вручную создаете base<derived>
тогда функции базового класса будут считать объект derived
, но это не так — это просто base<derived>
, В результате происходят странные вещи (неопределенное поведение). (Как отмечено в другом ответе, вы также распечатываете что-то, не устанавливая это …)
Что касается вашего второго вопроса, проверки, сгенерированные MSVC для обнаружения подобных ошибок программиста, по-видимому, отключены в режиме выпуска, предположительно по соображениям производительности.
Что касается вашего кода, показанного в main()
:
int main() {
base<derived> tmp;
tmp.print();
return 0;
}
base<derived> tmp;
это неправильное использование этого шаблона, вы хотели derived tmp;
,
Суть CRTP является то, что производный класс введенный к базовому классу и предоставляет реализации для определенных функций, которые разрешаются во время компиляции.
Базовый класс изначально предназначен для реализации исключительно по наследству. Таким образом, вы должны убедиться, что не было никаких экземпляров вне контекста наследования.
Чтобы пользователи вашей реализации не оказались в ловушке этого заблуждения, сделайте base
Защищенный конструктор (ы):
template <class T>
class base {
public:
~base() {
static_cast<T*>(this)->destroyData();
}
// ...
protected:
T* thisPtr;
base() : thisPtr(static_cast<T*>(this)) {
}
};
НОТА: Базовый класс не должен вызывать какие-либо методы, зависящие от полностью инициализированного derived
Методы класса внутри вашего конструктора базового класса, как вы показали на примере (static_cast<T*>(this)->setData();
)!
Вы можете хранить ссылку на T*
для дальнейшего использования, как показано в примере выше.
class derived : public base<derived> {
public:
derived() {
setData();
}
void destroyData() {
delete x;
x = nullptr;
}
void print_int() {
std::cout << "x = " << *x << std::endl;
}
private: // ????
derived(const derived& other) {}
inline derived& operator= (derived copy) {}
int *x;
};
Также немного неясно, почему вы хотите скрыть конструктор копирования и оператор присваивания для вашего класса?
template <class T> class mix_in:public T {
public:
base() { (this)->setData(); }
~base() { (this)->destroyData(); }
void print() { (this)->print_int(); }
};
переименовывать derived
в foo
, Не наследуй от mix_in
или же base
в foo
,
Своп base
за mix_in
в main
,
CRTP не является решением всех проблем.
Вы говорите в комментариях,
производный класс никогда не будет создан, его конструктор никогда не вызывается
Другие уже указали на неопределенное использование неструктурированного объекта. Чтобы быть конкретным, учтите, что int *x
это derived
член, и ваш base<derived>
использование генерирует вызовы, последовательно, чтобы derived::set_data()
, derived::print_int()
и на выходе derived::destroy_data()
все функции-члены объекта, который не существует, ссылаясь на члена, для которого вы никогда не выделяли место1.
Но я думаю, что то, что вы ищете, вполне законно. Факторинг кода — это весь смысл шаблонов и наследования, то есть, чтобы сделать важный код легче даже идентифицировать, не говоря уже о понимании, и любой последующий рефакторинг легче сделать, абстрагируя шаблон.
Так:
template < class T >
struct preconstruction_access_subset {}; // often left empty
template < class T, class S = preconstruction_access_subset<T> >
struct base : S {
base() { S::setData(); }
~base() { S::destroyData(); }
void print() { S::print_int(); }
};
// ... later ...
#include <iostream>
struct derived;
template<> struct preconstruction_access_subset<derived> {
// everything structors in `base<derived>` need
int *x;
void setData() { x = new int; }
void destroyData() { delete x; }
void print_int() { std::cout << x << '\n'; }
};
struct derived : base<derived> {
// everything no structors in any base class need to access
};
int main() {
base<derived> tmp;
tmp.print();
}
В этом примере print_int()
фактически не доступен во время строительства, так что вы можете вернуть его обратно в derived
Класс и доступ к нему из «базы», как для «обычного» CRTP, это просто вопрос того, что является наиболее понятным и лучшим для вашего реального кода.
1 Это не единственная проблема, только единственная, которая очевидна без учета какой-либо зависимости, которую сам компилятор, возможно, должен будет поместить в последовательность построения. Подумайте о связывании с независимым именем и потенциальном встраивании, а также о путях изменения идентичности объекта во время {de, con} построения и факторизации кода компилятора для всего потенциального шаблона / наследования / функции-члена / и т.д. комбинации. не просто поднять int *x;
чтобы решить текущий симптом, это просто пнул бы проблему дальше по дороге, даже если ваша еще не зашла так далеко.