Глава 7 готова

This commit is contained in:
Alexander Zhirov 2023-02-28 10:59:52 +03:00
parent 428eaab881
commit f10bcce1e4
1 changed files with 65 additions and 1 deletions

View File

@ -27,7 +27,7 @@
- [7.3.1. Перечисляемые типы](#7-3-1-перечисляемые-типы) - [7.3.1. Перечисляемые типы](#7-3-1-перечисляемые-типы)
- [7.3.2. Свойства перечисляемых типов](#7-3-2-свойства-перечисляемых-типов) - [7.3.2. Свойства перечисляемых типов](#7-3-2-свойства-перечисляемых-типов)
- [7.4. alias](#7-4-alias) - [7.4. alias](#7-4-alias)
- [7.5. Параметризированные контексты (конструкция template)](#75-параметризированные-контексты-конструкция-template) - [7.5. Параметризированные контексты (конструкция template)](#7-5-параметризированные-контексты-конструкция-template)
- [7.5.1. Одноименные шаблоны](#7-5-1-одноименные-шаблоны) - [7.5.1. Одноименные шаблоны](#7-5-1-одноименные-шаблоны)
- [7.5.2. Параметр шаблона this](#7-5-2-параметр-шаблона-this-4) - [7.5.2. Параметр шаблона this](#7-5-2-параметр-шаблона-this-4)
- [7.6. Инъекции кода с помощью конструкции mixin template](#7-6-инъекции-кода-с-помощью-конструкции-mixin-template) - [7.6. Инъекции кода с помощью конструкции mixin template](#7-6-инъекции-кода-с-помощью-конструкции-mixin-template)
@ -128,6 +128,8 @@ unittest
В отличие от имен `c1` и `с2`, допускающих привязку к любому объекту, имена `s1` и `s2` прочно привязаны к реальным объектам. Нет способа заставить два имени ссылаться на один и тот же объект-структуру (кроме ключевого слова `alias`, задающего простую эквивалентность имен; см. раздел 7.4), и не бывает имени структуры без закрепленного за ним значения, так что сравнение `s1 is null` бессмысленно и порождает ошибку во время компиляции. В отличие от имен `c1` и `с2`, допускающих привязку к любому объекту, имена `s1` и `s2` прочно привязаны к реальным объектам. Нет способа заставить два имени ссылаться на один и тот же объект-структуру (кроме ключевого слова `alias`, задающего простую эквивалентность имен; см. раздел 7.4), и не бывает имени структуры без закрепленного за ним значения, так что сравнение `s1 is null` бессмысленно и порождает ошибку во время компиляции.
[В начало ⮍](#7-1-1-семантика-копирования) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.2. Передача объекта-структуры в функцию ### 7.1.2. Передача объекта-структуры в функцию
Поскольку объект типа `struct` ведет себя как значение, он и передается в функцию по значению. Поскольку объект типа `struct` ведет себя как значение, он и передается в функцию по значению.
@ -157,6 +159,8 @@ void fun(ref S s) // fun получает ссылку
Раз уж мы заговорили о `ref`, отметим, что `this` передается по ссылке внутрь методов структуры `S` в виде скрытого параметра `ref S`. Раз уж мы заговорили о `ref`, отметим, что `this` передается по ссылке внутрь методов структуры `S` в виде скрытого параметра `ref S`.
[В начало ⮍](#7-1-2-передача-объекта-структуры-в-функцию) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.3. Жизненный цикл объекта-структуры ### 7.1.3. Жизненный цикл объекта-структуры
В отличие от объектов-классов, объектам-структурам не свойственно бесконечное время жизни (lifetime). Время жизни для них четко ограничено так же как для временных (стековых) объектов функций. Чтобы создать объект-структуру, задайте имя нужного типа, как если бы вы вызывали функцию: В отличие от объектов-классов, объектам-структурам не свойственно бесконечное время жизни (lifetime). Время жизни для них четко ограничено так же как для временных (стековых) объектов функций. Чтобы создать объект-структуру, задайте имя нужного типа, как если бы вы вызывали функцию:
@ -194,6 +198,8 @@ unittest
Поначалу может раздражать разница в синтаксисе выражения, создающего объект-структуру `Test(‹аргументы›)`, и выражения, создающего объект-класс `new Test(‹аргументы›)`. D мог бы отказаться от использования ключевого слова new при создании объектов-классов, но это `new` напоминает программисту, что выполняется операция выделения памяти (то есть необычное действие). Поначалу может раздражать разница в синтаксисе выражения, создающего объект-структуру `Test(‹аргументы›)`, и выражения, создающего объект-класс `new Test(‹аргументы›)`. D мог бы отказаться от использования ключевого слова new при создании объектов-классов, но это `new` напоминает программисту, что выполняется операция выделения памяти (то есть необычное действие).
[В начало ⮍](#7-1-3-жизненный-цикл-объекта-структуры) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.1. Конструкторы #### 7.1.3.1. Конструкторы
Конструктор структуры определяется так же, как конструктор класса (см. раздел 6.3.1): Конструктор структуры определяется так же, как конструктор класса (см. раздел 6.3.1):
@ -240,6 +246,8 @@ struct Test
Зачем нужно такое ограничение? Все из-за `T.init` значения по умолчанию, определяемого каждым типом. Оно должно быть статически известно, что противоречит существованию конструктора по умолчанию, выполняющего произвольный код. (Для классов `T.init` это пустая ссылка `null`, а не объект, построенный по умолчанию.) Правило для всех структур: конструктор по умолчанию инициализирует все поля объекта-структуры значениями по умолчанию. Зачем нужно такое ограничение? Все из-за `T.init` значения по умолчанию, определяемого каждым типом. Оно должно быть статически известно, что противоречит существованию конструктора по умолчанию, выполняющего произвольный код. (Для классов `T.init` это пустая ссылка `null`, а не объект, построенный по умолчанию.) Правило для всех структур: конструктор по умолчанию инициализирует все поля объекта-структуры значениями по умолчанию.
[В начало ⮍](#7-1-3-1-конструкторы) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.2. Делегирование конструкторов #### 7.1.3.2. Делегирование конструкторов
Скопируем пример из раздела 6.3.2 с заменой ключевого слова `class` на `struct`: Скопируем пример из раздела 6.3.2 с заменой ключевого слова `class` на `struct`:
@ -263,6 +271,8 @@ struct Widget
Код запускается, не требуя внесения каких-либо других изменений. Так же как и классы, структуры позволяют одному конструктору делегировать построение объекта другому конструктору с теми же ограничениями. Код запускается, не требуя внесения каких-либо других изменений. Так же как и классы, структуры позволяют одному конструктору делегировать построение объекта другому конструктору с теми же ограничениями.
[В начало ⮍](#7-1-3-2-делегирование-конструкторов) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.3. Алгоритм построения #### 7.1.3.3. Алгоритм построения
Классу приходится заботиться о выделении динамической памяти и инициализации своего базового подобъекта (см. раздел 6.3.3). Со структурами все гораздо проще, поскольку выделение памяти явный шаг алгоритма построения. Алгоритм построения объекта-структуры типа `T` по шагам: Классу приходится заботиться о выделении динамической памяти и инициализации своего базового подобъекта (см. раздел 6.3.3). Со структурами все гораздо проще, поскольку выделение памяти явный шаг алгоритма построения. Алгоритм построения объекта-структуры типа `T` по шагам:
@ -272,6 +282,8 @@ struct Widget
Если инициализация некоторых или всех полей структуры выглядит как `= void`, объем работ на первом шаге можно сократить, хотя и редко намного, зато такой маневр часто порождает трудноуловимые ошибки в вашем коде (тем не менее случай оправданного применения сокращенной инициализации иллюстрирует пример с классом `Transmogrifier` в разделе 6.3.3). Если инициализация некоторых или всех полей структуры выглядит как `= void`, объем работ на первом шаге можно сократить, хотя и редко намного, зато такой маневр часто порождает трудноуловимые ошибки в вашем коде (тем не менее случай оправданного применения сокращенной инициализации иллюстрирует пример с классом `Transmogrifier` в разделе 6.3.3).
[В начало ⮍](#7-1-3-3-алгоритм-построения) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.4. Конструктор копирования this(this) #### 7.1.3.4. Конструктор копирования this(this)
Предположим, требуется определить объект, который содержит локальный (`private`) массив и предоставляет ограниченный API для манипуляции этим массивом: Предположим, требуется определить объект, который содержит локальный (`private`) массив и предоставляет ограниченный API для манипуляции этим массивом:
@ -412,6 +424,8 @@ unittest
Вкратце, если вы определите для некоторой структуры конструктор копирования `this(this)`, компилятор позаботится о том, чтобы конструктор копирования вызывался в каждом случае копирования этого объекта-структуры независимо от того, является ли он самостоятельным объектом или частью более крупного объекта-структуры. Вкратце, если вы определите для некоторой структуры конструктор копирования `this(this)`, компилятор позаботится о том, чтобы конструктор копирования вызывался в каждом случае копирования этого объекта-структуры независимо от того, является ли он самостоятельным объектом или частью более крупного объекта-структуры.
[В начало ⮍](#7-1-3-4-конструктор-копирования-this-this) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.5. Аргументы в пользу this(this) #### 7.1.3.5. Аргументы в пользу this(this)
Зачем был введен конструктор копирования? Ведь ничего подобного в других языках пока нет. Почему бы просто не передавать исходный объект в будущую копию (как это делает C++)? Зачем был введен конструктор копирования? Ведь ничего подобного в других языках пока нет. Почему бы просто не передавать исходный объект в будущую копию (как это делает C++)?
@ -510,6 +524,8 @@ unittest
Вызов функции `move` гарантирует, что `w` будет перемещена, а ее содержимое будет заменено пустым, сконструированным по умолчанию объектом типа `Widget`. Кстати, это один из тех случаев, где пригодится неизменяемый и не порождающий исключения конструктор по умолчанию `Widget.init` (см. раздел 7.1.3.1). Без него сложно было бы найти способ оставить источник перемещения в строго определенном пустом состоянии. Вызов функции `move` гарантирует, что `w` будет перемещена, а ее содержимое будет заменено пустым, сконструированным по умолчанию объектом типа `Widget`. Кстати, это один из тех случаев, где пригодится неизменяемый и не порождающий исключения конструктор по умолчанию `Widget.init` (см. раздел 7.1.3.1). Без него сложно было бы найти способ оставить источник перемещения в строго определенном пустом состоянии.
[В начало ⮍](#7-1-3-5-аргументы-в-пользу-this-this) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.6. Уничтожение объекта и освобождение памяти #### 7.1.3.6. Уничтожение объекта и освобождение памяти
Структура может определять деструктор с именем `~this()`: Структура может определять деструктор с именем `~this()`:
@ -559,6 +575,8 @@ void main()
Освобождение памяти объекта-структуры по идее выполняется сразу же после деструкции. Освобождение памяти объекта-структуры по идее выполняется сразу же после деструкции.
[В начало ⮍](#7-1-3-6-уничтожение-объекта-и-освобождение-памяти) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.3.7. Алгоритм уничтожения структуры #### 7.1.3.7. Алгоритм уничтожения структуры
По умолчанию объекты-структуры уничтожаются в порядке, строго обратном порядку их создания. То есть первым уничтожается объект-структура, определенный в заданной области видимости последним: По умолчанию объекты-структуры уничтожаются в порядке, строго обратном порядку их создания. То есть первым уничтожается объект-структура, определенный в заданной области видимости последним:
@ -610,6 +628,8 @@ void main()
Можно явно инициировать вызов деструктора объекта-структуры с помощью инструкции `clear(объект);`. С функцией `clear` мы уже познакомились в разделе 6.3.5. Тогда она оказалась полезной для уничтожения состояния объекта-класса. Для объектов-структур функция `clear` делает то же самое: вызывает деструктор, а затем копирует биты значения `.init` в область памяти объекта. В результате получается правильно сконструированный объект, правда, без какого-либо интересного содержания. Можно явно инициировать вызов деструктора объекта-структуры с помощью инструкции `clear(объект);`. С функцией `clear` мы уже познакомились в разделе 6.3.5. Тогда она оказалась полезной для уничтожения состояния объекта-класса. Для объектов-структур функция `clear` делает то же самое: вызывает деструктор, а затем копирует биты значения `.init` в область памяти объекта. В результате получается правильно сконструированный объект, правда, без какого-либо интересного содержания.
[В начало ⮍](#7-1-3-7-алгоритм-уничтожения-структуры) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.4. Статические конструкторы и деструкторы ### 7.1.4. Статические конструкторы и деструкторы
Структура может определять любое число статических конструкторов и деструкторов. Это средство полностью идентично одноименному средству для классов, с которым мы уже встречались в разделе 6.3.6. Структура может определять любое число статических конструкторов и деструкторов. Это средство полностью идентично одноименному средству для классов, с которым мы уже встречались в разделе 6.3.6.
@ -658,6 +678,8 @@ void main()
Порядок выполнения очевиден для статических конструкторов и деструкторов, расположенных внутри одного модуля, но в случае нескольких модулей не всегда все так же ясно. Порядок выполнения статических конструкторов и деструкторов из разных модулей определен в разделе 6.3.6. Порядок выполнения очевиден для статических конструкторов и деструкторов, расположенных внутри одного модуля, но в случае нескольких модулей не всегда все так же ясно. Порядок выполнения статических конструкторов и деструкторов из разных модулей определен в разделе 6.3.6.
[В начало ⮍](#7-1-4-статические-конструкторы-и-деструкторы) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.5. Методы ### 7.1.5. Методы
Структуры могут определять функции-члены, также называемые методами. Поскольку в случае структур о наследовании и переопределении речи нет, методы структур лишь немногим больше, чем функции. Структуры могут определять функции-члены, также называемые методами. Поскольку в случае структур о наследовании и переопределении речи нет, методы структур лишь немногим больше, чем функции.
@ -702,6 +724,8 @@ struct S
Некоторые особые методы заслуживают более тщательного рассмотрения. К ним относятся оператор присваивания `opAssign`, используемый оператором `=`, оператор равенства `opEquals`, используемый операторами `==` и `!=`, а также упорядочивающий оператор `opCmp`, используемый операторами `<`, `<=`, `>=` и `>`. На самом деле, эта тема относится к главе 12, так как затрагивает вопрос перегрузки операторов, но эти операторы особенные: компилятор может сгенерировать их автоматически, со всем их особым поведением. Некоторые особые методы заслуживают более тщательного рассмотрения. К ним относятся оператор присваивания `opAssign`, используемый оператором `=`, оператор равенства `opEquals`, используемый операторами `==` и `!=`, а также упорядочивающий оператор `opCmp`, используемый операторами `<`, `<=`, `>=` и `>`. На самом деле, эта тема относится к главе 12, так как затрагивает вопрос перегрузки операторов, но эти операторы особенные: компилятор может сгенерировать их автоматически, со всем их особым поведением.
[В начало ⮍](#7-1-5-методы) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.5.1. Оператор присваивания #### 7.1.5.1. Оператор присваивания
По умолчанию, если задать: По умолчанию, если задать:
@ -785,6 +809,8 @@ ref Widget opAssign(ref Widget rhs)
} }
``` ```
[В начало ⮍](#7-1-5-1-оператор-присваивания) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.5.2. Сравнение структур на равенство #### 7.1.5.2. Сравнение структур на равенство
Средство для сравнения объектов-структур предоставляется «в комплекте» это операторы `==` и `!=`. Сравнение представляет собой поочередное сравнение внутренних элементов объектов и возвращает `false`, если хотя бы два соответствующих друг другу элемента сравниваемых объектов не равны, иначе результатом сравнения является `true`. Средство для сравнения объектов-структур предоставляется «в комплекте» это операторы `==` и `!=`. Сравнение представляет собой поочередное сравнение внутренних элементов объектов и возвращает `false`, если хотя бы два соответствующих друг другу элемента сравниваемых объектов не равны, иначе результатом сравнения является `true`.
@ -867,6 +893,8 @@ a.leftBottom.opEquals(b.leftBottom) && a.rightTop.opEquals(b.rightTop)
Этот пример также показывает, что сравнение выполняется в порядке объявления полей (т. е. поле `leftBottom` проверяется до проверки `rightTop`), и если встретились два неравных поля, сравнение завершается до того, как будут проверены все поля, благодаря сокращенному вычислению логических связок, построенных с помощью оператора `&&` (short circuit evaluation). Этот пример также показывает, что сравнение выполняется в порядке объявления полей (т. е. поле `leftBottom` проверяется до проверки `rightTop`), и если встретились два неравных поля, сравнение завершается до того, как будут проверены все поля, благодаря сокращенному вычислению логических связок, построенных с помощью оператора `&&` (short circuit evaluation).
[В начало ⮍](#7-1-5-2-сравнение-структур-на-равенство) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.6. Статические внутренние элементы ### 7.1.6. Статические внутренние элементы
Структура может определять статические данные и статические внутренние функции. Помимо ограниченной видимости и подчинения правилам доступа (см. раздел 7.1.7) режим работы статических внутренних функций ничем не отличается от режима работы обычных функций. Нет скрытого параметра `this`, не вовлечены никакие другие особые механизмы. Структура может определять статические данные и статические внутренние функции. Помимо ограниченной видимости и подчинения правилам доступа (см. раздел 7.1.7) режим работы статических внутренних функций ничем не отличается от режима работы обычных функций. Нет скрытого параметра `this`, не вовлечены никакие другие особые механизмы.
@ -913,6 +941,8 @@ void main()
[5, 3] [5, 3]
``` ```
[В начало ⮍](#7-1-6-статические-внутренние-элементы) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.7. Спецификаторы доступа ### 7.1.7. Спецификаторы доступа
Структуры подчиняются спецификаторам доступа `private` (см. раздел 6.7.1), `package` (см. раздел 6.7.2), `public` (см. раздел 6.7.4) и `export` (см. раздел 6.7.5) тем же образом, что и классы. Спецификатор `protected` применительно к структурам не имеет смысла, поскольку структуры не поддерживают наследование. Структуры подчиняются спецификаторам доступа `private` (см. раздел 6.7.1), `package` (см. раздел 6.7.2), `public` (см. раздел 6.7.4) и `export` (см. раздел 6.7.5) тем же образом, что и классы. Спецификатор `protected` применительно к структурам не имеет смысла, поскольку структуры не поддерживают наследование.
@ -931,6 +961,8 @@ struct S
Заметим, что хотя ключевое слово `export` разрешено везде, где синтаксис допускает применение спецификатора доступа, семантика этого ключевого слова зависит от реализации. Заметим, что хотя ключевое слово `export` разрешено везде, где синтаксис допускает применение спецификатора доступа, семантика этого ключевого слова зависит от реализации.
[В начало ⮍](#7-1-7-спецификаторы-доступа) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.8. Вложенность структур и классов ### 7.1.8. Вложенность структур и классов
Часто бывает удобно вложить в структуру другую структуру или класс. Например, контейнер дерева можно представить как оболочку-структуру с простым интерфейсом поиска, а внутри нее для определения узлов дерева использовать полиморфизм. Часто бывает удобно вложить в структуру другую структуру или класс. Например, контейнер дерева можно представить как оболочку-структуру с простым интерфейсом поиска, а внутри нее для определения узлов дерева использовать полиморфизм.
@ -999,6 +1031,8 @@ class Window
В отличие от классов, вложенных в другие классы, вложенные структуры и классы, вложенные в другие структуры, не обладают никаким скрытым внутренним элементом `outer` никакой специальный код не генерируется. Такие вложенные типы определяются в основном со структурной целью чтобы получить нужное управление доступом. В отличие от классов, вложенных в другие классы, вложенные структуры и классы, вложенные в другие структуры, не обладают никаким скрытым внутренним элементом `outer` никакой специальный код не генерируется. Такие вложенные типы определяются в основном со структурной целью чтобы получить нужное управление доступом.
[В начало ⮍](#7-1-8-вложенность-структур-и-классов) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.9. Структуры, вложенные в функции ### 7.1.9. Структуры, вложенные в функции
Вспомним, что говорилось в разделе 6.11.1: вложенные классы находятся в привилегированном положении, ведь они обладают особыми, уникальными свойствами. Вложенному классу доступны параметры и локальные переменные включающей функции. Если вы возвращаете вложенный класс в качестве результата функции, компилятор даже размещает кадр функции в динамической памяти, чтобы параметры и локальные переменные функции выжили после того, как она вернет управление. Вспомним, что говорилось в разделе 6.11.1: вложенные классы находятся в привилегированном положении, ведь они обладают особыми, уникальными свойствами. Вложенному классу доступны параметры и локальные переменные включающей функции. Если вы возвращаете вложенный класс в качестве результата функции, компилятор даже размещает кадр функции в динамической памяти, чтобы параметры и локальные переменные функции выжили после того, как она вернет управление.
@ -1035,6 +1069,8 @@ unittest
Вложенные структуры практически бесполезны, разве что, по сравнению со вложенными классами, позволяют избежать беспричинного ограничения. Функции не могут возвращать объекты вложенных структур, так как вызывающему их коду недоступна информация о типах таких объектов. Используя замысловатые вложенные структуры, код неявно побуждает создавать все больше сложных функций, а в идеале именно этого надо избегать в первую очередь. Вложенные структуры практически бесполезны, разве что, по сравнению со вложенными классами, позволяют избежать беспричинного ограничения. Функции не могут возвращать объекты вложенных структур, так как вызывающему их коду недоступна информация о типах таких объектов. Используя замысловатые вложенные структуры, код неявно побуждает создавать все больше сложных функций, а в идеале именно этого надо избегать в первую очередь.
[В начало ⮍](#7-1-9-структуры-вложенные-в-функции) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.1.10. Порождение подтипов в случае структур. Атрибут @disable ### 7.1.10. Порождение подтипов в случае структур. Атрибут @disable
К структурам неприменимы наследование и полиморфизм, но этот тип данных по-прежнему поддерживает конструкцию `alias this`, впервые представленную в разделе 6.13. С помощью `alias this` можно сделать структуру подтипом любого другого типа. Определим, к примеру, простой тип `Final`, поведением очень напоминающий ссылку на класс во всем, кроме того что переменную типа `Final` невозможно перепривязать! Пример использования переменной `Final`: К структурам неприменимы наследование и полиморфизм, но этот тип данных по-прежнему поддерживает конструкцию `alias this`, впервые представленную в разделе 6.13. С помощью `alias this` можно сделать структуру подтипом любого другого типа. Определим, к примеру, простой тип `Final`, поведением очень напоминающий ссылку на класс во всем, кроме того что переменную типа `Final` невозможно перепривязать! Пример использования переменной `Final`:
@ -1174,6 +1210,8 @@ struct Final(T)
Соблюдение такого соглашения сводит к минимуму риск непредвиденных коллизий. (Конечно, иногда можно намеренно перехватывать некоторые методы, оставив вызовы к ним за перехватчиком.) Соблюдение такого соглашения сводит к минимуму риск непредвиденных коллизий. (Конечно, иногда можно намеренно перехватывать некоторые методы, оставив вызовы к ним за перехватчиком.)
[В начало ⮍](#7-1-10-порождение-подтипов-в-случае-структур-атрибут-disable) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.11. Взаимное расположение полей. Выравнивание #### 7.1.11. Взаимное расположение полей. Выравнивание
Как располагаются поля в объекте-структуре? D очень консервативен в отношении структур: он располагает элементы их содержимого в том же порядке, в каком они указаны в определении структуры, но сохраняет за собой право вставлять между полями *отступы* (*padding*). Рассмотрим пример: Как располагаются поля в объекте-структуре? D очень консервативен в отношении структур: он располагает элементы их содержимого в том же порядке, в каком они указаны в определении структуры, но сохраняет за собой право вставлять между полями *отступы* (*padding*). Рассмотрим пример:
@ -1238,6 +1276,8 @@ void main()
D гарантирует, что все байты отступов последовательно заполняются нулями. D гарантирует, что все байты отступов последовательно заполняются нулями.
[В начало ⮍](#7-1-11-взаимное-расположение-полей-выравнивание) [Наверх ⮍](#7-другие-пользовательские-типы)
#### 7.1.11.1. Атрибут align #### 7.1.11.1. Атрибут align
Чтобы перекрыть выбор компилятора, определив собственное выравнивание, что повлияет на вставляемые отступы, объявляйте поля с атрибутом `align`. Такое переопределение может понадобиться для взаимодействия с определенной аппаратурой или для работы по бинарному протоколу, задающему особое выравнивание. Пример атрибута `align` в действии: Чтобы перекрыть выбор компилятора, определив собственное выравнивание, что повлияет на вставляемые отступы, объявляйте поля с атрибутом `align`. Такое переопределение может понадобиться для взаимодействия с определенной аппаратурой или для работы по бинарному протоколу, задающему особое выравнивание. Пример атрибута `align` в действии:
@ -1278,6 +1318,8 @@ struct Node
Если этот код выполнит присваивание `объект.next = new Node` (то есть заполнит `объект.next` ссылкой, контролируемой сборщиком мусора), хаос обеспечен: неверно выровненная ссылка пропадает из поля зрения сборщика мусора, память может быть освобождена, и `объект.next` превращается в «висячий» указатель. Если этот код выполнит присваивание `объект.next = new Node` (то есть заполнит `объект.next` ссылкой, контролируемой сборщиком мусора), хаос обеспечен: неверно выровненная ссылка пропадает из поля зрения сборщика мусора, память может быть освобождена, и `объект.next` превращается в «висячий» указатель.
[В начало ⮍](#7-1-11-1-атрибут-align) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.2. Объединение ## 7.2. Объединение
Объединения в стиле C можно использовать и в D, но не забывайте, что делать это нужно редко и с особой осторожностью. Объединения в стиле C можно использовать и в D, но не забывайте, что делать это нужно редко и с особой осторожностью.
@ -1386,6 +1428,8 @@ unittest
В конце концов вы должны понять, что объединение не такое уж зло, каким может показаться. Как правило, использовать объединение вместо того, чтобы играть типами с помощью выражения `cast`, хороший тон в общении между программистом и компилятором. Объединение указателя и целого числа указывает сборщику мусора, что ему следует быть осторожнее и не собирать этот указатель. Если вы сохраните указатель в целом числе и будете время от времени преобразовывать его назад к типу указателя (с помощью `cast`), результаты окажутся непредсказуемыми, ведь сборщик мусора может забрать память, ассоциированную с этим тайным указателем. В конце концов вы должны понять, что объединение не такое уж зло, каким может показаться. Как правило, использовать объединение вместо того, чтобы играть типами с помощью выражения `cast`, хороший тон в общении между программистом и компилятором. Объединение указателя и целого числа указывает сборщику мусора, что ему следует быть осторожнее и не собирать этот указатель. Если вы сохраните указатель в целом числе и будете время от времени преобразовывать его назад к типу указателя (с помощью `cast`), результаты окажутся непредсказуемыми, ведь сборщик мусора может забрать память, ассоциированную с этим тайным указателем.
[В начало ⮍](#7-2-объединение) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.3. Перечисляемые значения ## 7.3. Перечисляемые значения
Типы, принимающие всего несколько определенных значений, оказались очень полезными настолько полезными, что язык Java после нескольких лет героических попыток эмулировать перечисляемые типы с помощью идиомы в конце концов добавил их к основным типам. Определить хорошие перечисляемые типы непросто в C (и особенно в C++) типу `enum` присущи свои странности. D попытался учесть предшествующий опыт, определив простое и полезное средство для работы с перечисляемыми типами. Типы, принимающие всего несколько определенных значений, оказались очень полезными настолько полезными, что язык Java после нескольких лет героических попыток эмулировать перечисляемые типы с помощью идиомы в конце концов добавил их к основным типам. Определить хорошие перечисляемые типы непросто в C (и особенно в C++) типу `enum` присущи свои странности. D попытался учесть предшествующий опыт, определив простое и полезное средство для работы с перечисляемыми типами.
@ -1444,6 +1488,8 @@ enum
Когда бы вы ни использовали, например, идентификатор `green`, код будет вести себя так, будто вместо этого идентификатора вы написали `Color(0, 255, 0)`. Когда бы вы ни использовали, например, идентификатор `green`, код будет вести себя так, будто вместо этого идентификатора вы написали `Color(0, 255, 0)`.
[В начало ⮍](#7-3-перечисляемые-значения) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.3.1. Перечисляемые типы ### 7.3.1. Перечисляемые типы
Можно дать имя группе перечисляемых значений, создав таким образом новый тип на ее основе: Можно дать имя группе перечисляемых значений, создав таким образом новый тип на ее основе:
@ -1487,6 +1533,8 @@ assert(F.a == F.d); // Тест пройден
Корень этой проблемы в том, что наибольшее значение типа `int`, которое может быть представлено значением типа `float`, равно `16_777_216`, и выход за эту границу сопровождается все возрастающими диапазонами целых значений, представляемых одним и тем же числом типа `float`. Корень этой проблемы в том, что наибольшее значение типа `int`, которое может быть представлено значением типа `float`, равно `16_777_216`, и выход за эту границу сопровождается все возрастающими диапазонами целых значений, представляемых одним и тем же числом типа `float`.
[В начало ⮍](#7-3-1-перечисляемые-типы) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.3.2. Свойства перечисляемых типов ### 7.3.2. Свойства перечисляемых типов
Для всякого перечисляемого типа `E` определены три свойства: `E.init` (это свойство принимает первое из значений, определенных в `E`), `E.min` (наименьшее из определенных в `E` значений) и `E.max` (наибольшее из определенных в `E` значений). Два последних значения определены, только если базовым типом `E` является тип, поддерживающий сравнение во время компиляции с помощью оператора `<`. Для всякого перечисляемого типа `E` определены три свойства: `E.init` (это свойство принимает первое из значений, определенных в `E`), `E.min` (наименьшее из определенных в `E` значений) и `E.max` (наибольшее из определенных в `E` значений). Два последних значения определены, только если базовым типом `E` является тип, поддерживающий сравнение во время компиляции с помощью оператора `<`.
@ -1518,6 +1566,8 @@ void main()
Рассмотренная функция `toString` уже реализована в модуле `std.conv` стандартной библиотеки, имеющем дело с общими преобразованиями. Имя этой функции немного отличается от того, что использовали мы: вам придется писать `to!string(w)` вместо `toString(w)`, что говорит о гибкости этой функции (также можно сделать вызов `to!dstring(w)` или `to!byte(w)` и т. д.). Этот же модуль определяет и обратную функцию, которая конвертирует строку в значение перечисляемого типа; например вызов `to!OddWord("acini")` возвращает `OddWord.acini`. Рассмотренная функция `toString` уже реализована в модуле `std.conv` стандартной библиотеки, имеющем дело с общими преобразованиями. Имя этой функции немного отличается от того, что использовали мы: вам придется писать `to!string(w)` вместо `toString(w)`, что говорит о гибкости этой функции (также можно сделать вызов `to!dstring(w)` или `to!byte(w)` и т. д.). Этот же модуль определяет и обратную функцию, которая конвертирует строку в значение перечисляемого типа; например вызов `to!OddWord("acini")` возвращает `OddWord.acini`.
[В начало ⮍](#7-3-2-свойства-перечисляемых-типов) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.4. alias ## 7.4. alias
В ряде случаев мы уже имели дело с `size_t` целым типом без знака, достаточно вместительным, чтобы представить размер любого объекта. Тип `size_t` не определен языком, он просто принимает форму `uint` или `ulong` в зависимости от адресного пространства конечной системы (32 или 64 бита соответственно). В ряде случаев мы уже имели дело с `size_t` целым типом без знака, достаточно вместительным, чтобы представить размер любого объекта. Тип `size_t` не определен языком, он просто принимает форму `uint` или `ulong` в зависимости от адресного пространства конечной системы (32 или 64 бита соответственно).
@ -1612,6 +1662,8 @@ else
С помощью объявления псевдоним `ptrdiff_t` привязывается к разным типам в зависимости от того, по какой ветке статического условия пойдет поток управления. Без этой возможности привязки код, которому потребовался такой тип, пришлось бы разместить в одной из веток `static if`. С помощью объявления псевдоним `ptrdiff_t` привязывается к разным типам в зависимости от того, по какой ветке статического условия пойдет поток управления. Без этой возможности привязки код, которому потребовался такой тип, пришлось бы разместить в одной из веток `static if`.
[В начало ⮍](#7-4-alias) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.5. Параметризированные контексты (конструкция template) ## 7.5. Параметризированные контексты (конструкция template)
Мы уже рассмотрели средства, облегчающие параметризацию во время компиляции (эти средства сродни шаблонам из C++ и родовым типам из языков Java и C#), это функции (см. раздел 5.3), параметризированные классы (см. раздел 6.14) и параметризированные структуры, которые подчиняются тем же правилам, что и параметризированные классы. Тем не менее иногда во время компиляции требуется каким-либо образом манипулировать типами, не определяя функцию, структуру или класс. Один из механизмов, подходящих под это описание (широко используемый в C++), выбор того или иного типа в зависимости от статически известного логического условия. При этом не определяется никакой новый тип и не вызывается никакая функция лишь создается псевдоним для одного из существующих типов. Мы уже рассмотрели средства, облегчающие параметризацию во время компиляции (эти средства сродни шаблонам из C++ и родовым типам из языков Java и C#), это функции (см. раздел 5.3), параметризированные классы (см. раздел 6.14) и параметризированные структуры, которые подчиняются тем же правилам, что и параметризированные классы. Тем не менее иногда во время компиляции требуется каким-либо образом манипулировать типами, не определяя функцию, структуру или класс. Один из механизмов, подходящих под это описание (широко используемый в C++), выбор того или иного типа в зависимости от статически известного логического условия. При этом не определяется никакой новый тип и не вызывается никакая функция лишь создается псевдоним для одного из существующих типов.
@ -1710,6 +1762,8 @@ template factorial(uint n)
Несмотря на то что `factorial` является совершенным функциональным определением, в данном случае это не лучший подход. При необходимости вычислять значения во время компиляции, пожалуй, стоило бы воспользоваться механизмом вычислений во время компиляции (см. раздел 5.12). В отличие от приведенного выше шаблона `factorial`, функция `factorial` более гибка, поскольку может вычисляться как во время компиляции, так и во время исполнения. Конструкция `template` больше всего подходит для манипуляции типами, имеющей место в `Select` и `isSomeString`. Несмотря на то что `factorial` является совершенным функциональным определением, в данном случае это не лучший подход. При необходимости вычислять значения во время компиляции, пожалуй, стоило бы воспользоваться механизмом вычислений во время компиляции (см. раздел 5.12). В отличие от приведенного выше шаблона `factorial`, функция `factorial` более гибка, поскольку может вычисляться как во время компиляции, так и во время исполнения. Конструкция `template` больше всего подходит для манипуляции типами, имеющей место в `Select` и `isSomeString`.
[В начало ⮍](#7-5-параметризированные-контексты-конструкция-template) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.5.1. Одноименные шаблоны ### 7.5.1. Одноименные шаблоны
Конструкция `template` может определять любое количество идентификаторов, но, как видно из предыдущих примеров, нередко в ней определен ровно один идентификатор. Обычно шаблон определяется лишь с целью решить единственную задачу и в качестве результата сделать доступным единственный идентификатор (такой как `Type` в случае `Select` или `value` в случае `isSomeString`). Конструкция `template` может определять любое количество идентификаторов, но, как видно из предыдущих примеров, нередко в ней определен ровно один идентификатор. Обычно шаблон определяется лишь с целью решить единственную задачу и в качестве результата сделать доступным единственный идентификатор (такой как `Type` в случае `Select` или `value` в случае `isSomeString`).
@ -1751,6 +1805,8 @@ unittest
Это сообщение об ошибке вызвано соблюдением правила об одноименности: перед тем как делать что-либо еще, компилятор расширяет вызов `isNumeric!(int)` до `isNumeric!(int).isNumeric`. Затем пользовательский код делает попытку заполучить значение `isNumeric!(int).isNumeric.test1`, что равносильно попытке получить внутренний элемент `test1` из логического значения, отсюда и сообщение об ошибке. Короче говоря, используйте одноименные шаблоны тогда и только тогда, когда хотите открыть доступ лишь к одному идентификатору. Этот случай скорее частый, чем редкий, поэтому одноименные шаблоны очень популярны и удобны. Это сообщение об ошибке вызвано соблюдением правила об одноименности: перед тем как делать что-либо еще, компилятор расширяет вызов `isNumeric!(int)` до `isNumeric!(int).isNumeric`. Затем пользовательский код делает попытку заполучить значение `isNumeric!(int).isNumeric.test1`, что равносильно попытке получить внутренний элемент `test1` из логического значения, отсюда и сообщение об ошибке. Короче говоря, используйте одноименные шаблоны тогда и только тогда, когда хотите открыть доступ лишь к одному идентификатору. Этот случай скорее частый, чем редкий, поэтому одноименные шаблоны очень популярны и удобны.
[В начало ⮍](#7-5-1-одноименные-шаблоны) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.5.2. Параметр шаблона this[^4] ### 7.5.2. Параметр шаблона this[^4]
Познакомившись с классами и структурами, можно параметризовать наш обобщенный метод типом неявного аргумента `this`. Например: Познакомившись с классами и структурами, можно параметризовать наш обобщенный метод типом неявного аргумента `this`. Например:
@ -1780,6 +1836,8 @@ unittest
Также параметр `this` удобен в случае, когда один метод нужно использовать для разных квалификаторов неизменяемости объекта (см. главу 8). Также параметр `this` удобен в случае, когда один метод нужно использовать для разных квалификаторов неизменяемости объекта (см. главу 8).
[В начало ⮍](#7-5-2-параметр-шаблона-this-4) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.6. Инъекции кода с помощью конструкции mixin template ## 7.6. Инъекции кода с помощью конструкции mixin template
При некоторых программных решениях приходится добавлять шаблонный код (такой как определения данных и методов) в одну или несколько реализаций классов. К типичным примерам относятся поддержка сериализации, шаблон проектирования «Наблюдатель» и передача событий в оконных системах. При некоторых программных решениях приходится добавлять шаблонный код (такой как определения данных и методов) в одну или несколько реализаций классов. К типичным примерам относятся поддержка сериализации, шаблон проектирования «Наблюдатель» и передача событий в оконных системах.
@ -1867,6 +1925,8 @@ assert(MyDouble.getX() == 5.5);
Таким образом, шаблоны `mixin` это *почти* как копирование и вставка; вы можете многократно копировать и вставлять код, а потом указывать, к какой именно вставке хотите обратиться. Таким образом, шаблоны `mixin` это *почти* как копирование и вставка; вы можете многократно копировать и вставлять код, а потом указывать, к какой именно вставке хотите обратиться.
[В начало ⮍](#7-6-инъекции-кода-с-помощью-конструкции-mixin-template) [Наверх ⮍](#7-другие-пользовательские-типы)
### 7.6.1. Поиск идентификаторов внутри mixin ### 7.6.1. Поиск идентификаторов внутри mixin
Самая большая разница между шаблоном `mixin` и обычным шаблоном (в том виде, как он определен в разделе 7.5), способная вызвать больше всего вопросов, это поиск имен. Самая большая разница между шаблоном `mixin` и обычным шаблоном (в том виде, как он определен в разделе 7.5), способная вызвать больше всего вопросов, это поиск имен.
@ -1911,6 +1971,8 @@ void main()
Склонность шаблонов `mixin` привязываться к локальным идентификаторам придает им выразительности, но следовать их логике становится сложно. Такое поведение делает шаблоны `mixin` применимыми лишь в ограниченном количестве случаев; прежде чем доставать из ящика с инструментами эти особенные ножницы, необходимо семь раз отмерить. Склонность шаблонов `mixin` привязываться к локальным идентификаторам придает им выразительности, но следовать их логике становится сложно. Такое поведение делает шаблоны `mixin` применимыми лишь в ограниченном количестве случаев; прежде чем доставать из ящика с инструментами эти особенные ножницы, необходимо семь раз отмерить.
[В начало ⮍](#7-6-1-поиск-идентификаторов-внутри-mixin) [Наверх ⮍](#7-другие-пользовательские-типы)
## 7.7. Итоги ## 7.7. Итоги
Классы позволяют эффективно представить далеко не любую абстракцию. Например, они не подходят для мелкокалиберных объектов, контекстно-зависимых ресурсов и типов значений. Этот пробел восполняют структуры. В частности, благодаря конструкторам и деструкторам легко определять типы контекстно-зависимых ресурсов. Классы позволяют эффективно представить далеко не любую абстракцию. Например, они не подходят для мелкокалиберных объектов, контекстно-зависимых ресурсов и типов значений. Этот пробел восполняют структуры. В частности, благодаря конструкторам и деструкторам легко определять типы контекстно-зависимых ресурсов.
@ -1925,6 +1987,8 @@ void main()
Кроме того, предлагаются параметризированные контексты, принимающие форму шаблонов `mixin`, которые во многом ведут себя подобно макросам. В будущем шаблоны `mixin` может заменить развитое средство AST-макросов. Кроме того, предлагаются параметризированные контексты, принимающие форму шаблонов `mixin`, которые во многом ведут себя подобно макросам. В будущем шаблоны `mixin` может заменить развитое средство AST-макросов.
[В начало ⮍](#7-7-итоги) [Наверх ⮍](#7-другие-пользовательские-типы)
[^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4). [^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4).
[^2]: Термин «клуктура» предложил Бартош Милевски. [^2]: Термин «клуктура» предложил Бартош Милевски.
[^3]: Кроме того, `код1` может сохранить указатель на значение `w`, которое использует `код2`. [^3]: Кроме того, `код1` может сохранить указатель на значение `w`, которое использует `код2`.