Взгляните на то, как Ruby on Rails делает это.
Первый существуют так называемые файлы миграции, которые в основном преобразовывают схему базы данных и данные от версии N до версии N+1 (или в случае понижения от версии N+1 до N). База данных имеет таблицу, которая говорит текущую версию.
базы данных Test всегда вытираются чистые перед модульными тестами и заполняются с фиксированными данными из файлов.
Какой компилятор вы используете? Статическое приведение от базового типа к производному типу может привести к корректировке указателя - особенно вероятно, если задействовано множественное наследование (что, похоже, не относится к вашей ситуации из вашего описания). Однако это все еще возможно без MI.
Стандарт указывает, что если выполняется приведение значения нулевого указателя, результатом будет значение нулевого указателя (статическое приведение 5.2.9 / 8). Однако я думаю, что на многих компиляторах большинство понижающих преобразований (особенно когда задействовано одиночное наследование) не приводят к корректировке указателя, поэтому я мог представить, что в компиляторе может быть ошибка, из-за которой он не будет выполнять специальную проверку на null, которая потребуется, чтобы избежать «преобразования» нулевого указателя с нулевым значением в какой-то бессмысленный указатель с ненулевым значением. Я предполагаю, что для существования такой ошибки вы должны делать что-то необычное, чтобы компилятор вынужден был корректировать указатель при понижающем преобразовании.
Было бы интересно посмотреть, какой тип ассемблерного кода был сгенерирован для вашего примера.
И для получения подробной информации о том, как компилятор может компоновить объект, который может нуждаться в корректировке указателя с помощью статических преобразований, Стэн Липпман «Внутри объектной модели C ++» является отличным ресурсом.
Статья Страуструпа по теме. Множественное наследование для C ++ (с 1989 г.) также является хорошим чтением. Это' Очень плохо, если в компиляторе C ++ есть ошибка, о которой я размышляю здесь - Страуструп подробно обсуждает проблему нулевого указателя в этой статье (4.5 указатели с нулевым значением).
По вашему второму вопросу:
Q2. Является ли static_casting от B до C / D / E допустимым?
Это совершенно верно до тех пор, пока вы выполняете приведение указателя B к указателю C / D / E, указатель B фактически указывает на под- объект C / D / E объекта (соответственно) и B не является виртуальной базой. Об этом говорится в том же параграфе стандарта (5.2.9 / 8 Static cast). Я выделил предложения параграфа, наиболее важные для ваших вопросов:
rvalue типа «указатель на cv1 B», где B - тип класса, может быть преобразовано в rvalue типа «указатель на cv2 D» , где D - класс, производный (раздел 10) от B, если существует допустимое стандартное преобразование из «указателя на D» в «указатель на B» (4.10), cv2 является той же квалификацией cv, что и cv1, или большей квалификацией cv, чем cv1, и B не является виртуальным базовым классом D . Значение нулевого указателя (4.10) преобразуется в значение нулевого указателя целевого типа. Если rvalue типа «указатель на cv1 B» указывает на B, который фактически является подобъектом объекта типа D, результирующий указатель указывает на включающий объект типа D. В противном случае результат преобразования не определен.
В заключение, вы можете обойти проблему, используя что-то вроде:
Set1(pEntity ? static_cast<C*>(pEntity) : 0);
, что компилятор должен делать за вас.
10) преобразуется в значение нулевого указателя целевого типа. Если rvalue типа «указатель на cv1 B» указывает на B, который фактически является подобъектом объекта типа D, результирующий указатель указывает на включающий объект типа D. В противном случае результат преобразования не определен.В заключение, вы можете обойти проблему, используя что-то вроде:
Set1(pEntity ? static_cast<C*>(pEntity) : 0);
, что компилятор должен делать за вас.
10) преобразуется в значение нулевого указателя целевого типа. Если rvalue типа «указатель на cv1 B» указывает на B, который фактически является подобъектом объекта типа D, результирующий указатель указывает на включающий объект типа D. В противном случае результат преобразования не определен.В заключение, вы можете обойти проблему, используя что-то вроде:
Set1(pEntity ? static_cast<C*>(pEntity) : 0);
, что компилятор должен делать за вас.
Вы можете static_cast
нулевой указатель - он даст вам нулевой указатель.
В вашем фрагменте проблема, скорее всего, заключается в том, что вы передаете несовместимые значения ] pEntity
и iMyEntityType
в функцию. Таким образом, когда выполняется static_cast
, он слепо приводит к неправильному типу (не к тому же типу, что и фактический объект), и вы получаете недопустимый указатель, который позже передается вниз по стеку вызовов и вызывает неопределенное поведение (сбой программа). dynamic_cast
в том же случае видит, что объект действительно не ожидаемого типа, и возвращает нулевой указатель.
static_cast
сам по себе не может вызвать сбой - его поведение во время выполнения такое же, как reinterpret_cast
. В вашем коде что-то не так.
MyClass * p = static_cast
работает хорошо.
Новое :
Если вы используете множественное наследование, то static_cast
может сместить указатель.
Рассмотрим следующий код:
struct B1 {};
struct B2 {};
struct A : B2, B1 {
virtual ~A() {}
};
Что такое struct A
?
A
содержит таблицу виртуальных функций и B1
и B2
.
B1
сдвигается относительно A
.
Чтобы преобразовать B1
в , компилятору
необходимо выполнить сдвиг назад.
Если указатель на B1
имеет значение NULL, то сдвиг дает недопустимый результат.
static_cast
предназначен для ситуаций, когда вы знаете , что приведение может быть выполнено (либо вы приводите к родительскому классу, либо у вас есть другие способы оценки типа класс). Тип не проверяется во время выполнения (отсюда static
). С другой стороны, dynamic_cast
проверяет во время выполнения, действительно ли объект относится к тому типу, к которому вы хотите его привести. Что касается reinterpret_cast
, он не делает ничего, кроме использования одной и той же памяти для разных целей. Обратите внимание, что reinterpret_cast
никогда не следует использовать для перехода от одного класса к другому.
В конце концов, причина сбоя static_cast
при сбое NULL указателя заключается в том, что static_cast
с наследованием может потребоваться немного арифметики указателя от компилятора. Это зависит от того, как компилятор на самом деле реализует наследование. Но в случае множественного наследования у него нет выбора.
Один из способов убедиться в этом состоит в том, что дочерний класс «содержит» родительский класс. Виртуальная таблица содержит одну из родительских, но с дополнительными функциями. Если функции добавлены в начале, то любое приведение к родительскому классу будет указывать на другое место ... откуда не могут быть видны функции дочернего класса. Я надеюсь, что это имеет смысл.
Замечание по арифметике указателей
Во-первых, это всегда так и для множественного наследования, но компилятор может сделать это и для одиночного наследования.
В основном, если вы посмотрите на макет памяти для содержимого объекта с помощью виртуальных методов, вы можете сделать что-то вроде:
+---------------+----------------+
| ptr to vtable | members .... |
+---------------+----------------+
В случае одиночного наследования этого в значительной степени достаточно. В частности, вы можете гарантировать, что vtable любого производного класса начинается с vtable родительского класса, а первые члены являются членами материнского класса.
Теперь, если у вас есть множественное наследование, все обстоит сложнее. В частности, вы, вероятно, не сможете последовательно объединить vtables и members (по крайней мере, в общем случае). Итак, скажем, вы унаследованы от классов A, B и C, у вас, вероятно, будет что-то вроде:
A B C
+----------------------+-----------+-----------+----------+-----------+-----+
| local vtable/members | vtable A | members A | vtable B | members B | ... |
+----------------------+-----------+-----------+----------+-----------+-----+
Таким образом, если вы укажете на A, вы увидите объект как объект типа A
, плюс остальное. Но если вы хотите видеть объект как имеющий тип B
, вам нужно указать адрес B и т. Д. Обратите внимание, что это может быть не совсем то, что делает система, но это мерзость ее.
A B C
+----------------------+-----------+-----------+----------+-----------+-----+
| local vtable/members | vtable A | members A | vtable B | members B | ... |
+----------------------+-----------+-----------+----------+-----------+-----+
Таким образом, если вы укажете на A, вы увидите объект как объект типа A
, плюс остальное. Но если вы хотите видеть объект как имеющий тип B
, вам нужно указать адрес B и т. Д. Обратите внимание, что это может быть не совсем то, что делает система, но это мерзость ее.
A B C
+----------------------+-----------+-----------+----------+-----------+-----+
| local vtable/members | vtable A | members A | vtable B | members B | ... |
+----------------------+-----------+-----------+----------+-----------+-----+
Таким образом, если вы укажете на A, вы увидите объект как объект типа A
, плюс остальное. Но если вы хотите видеть объект как имеющий тип B
, вам нужно указать адрес B и т. Д. Обратите внимание, что это может быть не совсем то, что делает система, но это мерзость ее.