В какой степени С++ - статически типизированный язык?
Раньше я думал, что ответ на этот вопрос "100%", но я недавно указал на пример, который заставляет задуматься дважды. Рассмотрим массив C, объявленный как объект с автоматической продолжительностью хранения:
int main()
{
int foo[42] = { 0 };
}
Здесь тип foo
явно int[42]
. Рассмотрим вместо этого этот случай:
int main()
{
int* foo = new int[rand() % 42];
delete[] foo;
}
Здесь тип foo
равен int*
, но как определить тип объекта, созданного выражением new
во время компиляции? (Акцент делается на том, что я не говорю о указателе, возвращаемом выражением new
, а скорее о объекте массива, созданном выражением new
).
Это то, что параграф 5.3.4/1 стандарта С++ 11 указывает на результат выражения new
:
[...] Объекты, созданные новым выражением, имеют динамическую продолжительность хранения (3.7.4). [Примечание: время жизни таких сущность не обязательно ограничивается областью, в которой она создана. -end note] Если объект не является массивом object, новое выражение возвращает указатель на созданный объект. Если это массив, новое выражение возвращает указатель на исходный элемент массива.
Раньше я думал, что в С++ тип всех объектов определяется во время компиляции, но приведенный выше пример, похоже, опровергает эту веру. Кроме того, в пункте 1.8/1:
[...] Свойства объекта определяются , когда объект создается. Объект может иметь имя (раздел 3). Объект имеет продолжительность хранения (3.7), которая влияет на его время жизни (3.8). Объект имеет тип (3.9). [...]
Итак, мои вопросы:
- Что подразумевается под "свойствами" в последнем цитируемом абзаце? Очевидно, что имя объекта не может считаться чем-то, что определено "когда объект создан" - если "созданный" здесь не означает нечто иное, чем я думаю;
- Существуют ли другие примеры объектов, тип которых определяется только во время выполнения?
- Насколько корректно говорить, что С++ - это статически типизированный язык? Вернее, что является самым правильным способом классификации С++ в этом отношении?
Было бы здорово, если бы кто-нибудь мог разработать хотя бы один из вышеуказанных пунктов.
EDIT:
Стандарт, похоже, дает понять, что выражение new
действительно создает объект массива, а не только несколько объектов, выложенных как массив, как указано некоторыми. В пункте 5.3.4/5 (любезно предоставлено Xeo):
Когда выделенным объектом является массив (т.е. используется синтаксис noptr-new-declarator или идентификатор нового типа или type-id обозначает тип массива), новое выражение дает указатель на исходный элемент (если есть) массива. [Примечание: оба new int
и new int[10]
имеют тип int*
, а тип new int[i][10]
- int (*)[10]
-end note] Атрибут-specifier-seq в noptr-new-declarator содержит для связанного типа массива.
Ответы
Ответ 1
Термины "статический тип" и "динамический тип" относятся к выражениям.
статический тип
тип выражения (3.9), полученный в результате анализа программы без рассмотрения семантики выполнения
динамический тип
<glvalue> тип самого производного объекта (1.8), к которому glvalue, обозначенное выражением glvalue, относится к
Кроме того, вы можете видеть, что динамический тип отличается от статического типа, когда может быть получен статический тип, что означает, что тип динамического массива всегда совпадает с типом выражения static.
Итак, ваш вопрос:
но как определить тип объекта, созданного новым выражением во время компиляции?
Объекты имеют типы, но они не являются "статическими" или "динамическими" типами, не содержат выражения, которые относятся к объекту. Учитывая выражение, статический тип всегда известен во время компиляции. В отсутствие вывода динамический тип такой же, как у статического типа.
Но вы спрашиваете об типах объектов, не зависящих от выражений. В приведенном примере вы попросили создать объект, но вы не укажете тип объекта, который хотите создать во время компиляции. Вы можете посмотреть на это следующим образом:
template<typename T>
T *create_array(size_t s) {
switch(s) {
case 1: return &(*new std::array<T, 1>)[0];
case 2: return &(*new std::array<T, 2>)[0];
// ...
}
}
Здесь мало особых или уникальных. Другая возможность:
struct B { virtual ~B() {}};
struct D : B {};
struct E : B {};
B *create() {
if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
return new D;
}
return new E;
}
Или:
void *create() {
if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
return reinterpret_cast<void*>(new int);
}
return reinterpret_cast<void*>(new float);
}
Единственное отличие от new int[]
заключается в том, что вы не можете видеть в его реализации, чтобы видеть, как он выбирает различные типы объектов для создания.
Ответ 2
Новое выражение не создает объект с типом массива, зависящим от времени выполнения. Он создает много объектов, каждый из которых имеет статический тип int
. Количество этих объектов неизвестно статически.
С++ предоставляет два случая (раздел 5.2.8) для динамического типа:
- То же, что и статический тип выражения
- Когда статический тип является полиморфным, тип времени выполнения самого производного объекта
Ни один из них не дает никакого объекта, созданного типом динамического массива new int[N]
.
Педантично оценка нового выражения создает бесконечное количество перекрывающихся объектов массива. Из 3.8p2:
[Примечание: время жизни объекта массива начинается, как только будет получено хранилище с надлежащим размером и выравниванием, а его время жизни заканчивается, когда хранилище, которое занимает массив, повторно используется или освобождается. 12.6.2 описывает время жизни базовых и членных подобъектов. - конечная нота]
Итак, если вы хотите поговорить о "объекте массива", созданном new int[5]
, вы должны указать ему не только тип int[5]
, но также int[4]
, int[1]
, char[5*sizeof(int)]
и struct s { int x; }[5]
.
Я утверждаю, что это эквивалентно утверждению, что типы массивов не существуют во время выполнения. Тип объекта должен быть ограничительным, информацией и сообщать вам о его свойствах. Разрешение области памяти обрабатываться как бесконечное количество перекрывающихся объектов массива с разным типом в действии означает, что объект массива совершенно бесплоден. Понятие типа времени выполнения имеет смысл только для объектов элемента, хранящихся в массиве.
Ответ 3
Раньше я думал, что в С++ тип всех объектов определяется во время компиляции, но приведенный выше пример, похоже, опровергает эту веру.
В примере, который вы цитируете, говорится о продолжительности хранения элемента. С++ распознает три длительности хранения:
- Статическая продолжительность хранения - это длительность глобальных и локальных статических переменных.
- Автоматическая продолжительность хранения - это продолжительность для локальных переменных "распределенная стек".
- Длительность динамического хранения - это продолжительность для динамически распределенной памяти, например, с
new
или malloc
.
Использование слова "динамический" здесь не имеет ничего общего с типом объекта. Это относится к тому, как реализация должна хранить данные, составляющие объект.
Раньше я думал, что в С++ тип всех объектов определяется во время компиляции, но приведенный выше пример, похоже, опровергает эту веру.
В вашем примере есть одна переменная, которая имеет тип int*
. Нет фактического типа массива для базового массива, который может быть восстановлен любым значимым образом в программе. Не происходит динамического ввода.