diff --git a/07-другие-пользовательские-типы/README.md b/07-другие-пользовательские-типы/README.md index e118945..077ef4d 100644 --- a/07-другие-пользовательские-типы/README.md +++ b/07-другие-пользовательские-типы/README.md @@ -1,29 +1,38 @@ # 7. Другие пользовательские типы - [7.1. Структуры](#7-1-структуры) - - [7.1.1. Семантика копирования]() - - [7.1.2. Передача объекта-структуры в функцию]() - - [7.1.3. Жизненный цикл объекта-структуры]() - - [7.1.3.1. Конструкторы]() - - [7.1.3.2. Делегирование конструкторов]() - - [7.1.3.3. Алгоритм построения]() - - [7.1.3.4. Конструктор копирования this(this)]() - - [7.1.3.5. Аргументы в пользу this(this)]() - - [7.1.3.6. Уничтожение объекта и освобождение памяти]() - - [7.1.3.7. Алгоритм уничтожения структуры]() - - [7.1.4. Статические конструкторы и деструкторы]() - - [7.1.5. Методы]() - - [7.1.5.1. Оператор присваивания]() - - [7.1.5.2. Сравнение структур на равенство]() - - [7.1.6. Статические внутренние элементы]() - - [7.1.7. Спецификаторы доступа]() - - [7.1.8. Вложенность структур и классов]() - - [7.1.9. Структуры, вложенные в функции]() - - [7.1.10. Порождение подтипов в случае структур. Атрибут @disable]() - - [7.1.11. Взаимное расположение полей. Выравнивание]() - - [7.1.11.1. Атрибут align]() -- [7.2. Объединение]() -- [7.3. Перечисляемые значения]() + - [7.1.1. Семантика копирования](#7-1-1-семантика-копирования) + - [7.1.2. Передача объекта-структуры в функцию](#7-1-2-передача-объекта-структуры-в-функцию) + - [7.1.3. Жизненный цикл объекта-структуры](#7-1-3-жизненный-цикл-объекта-структуры) + - [7.1.3.1. Конструкторы](#7-1-3-1-конструкторы) + - [7.1.3.2. Делегирование конструкторов](#7-1-3-2-делегирование-конструкторов) + - [7.1.3.3. Алгоритм построения](#7-1-3-3-алгоритм-построения) + - [7.1.3.4. Конструктор копирования this(this)](#7-1-3-4-конструктор-копирования-this-this) + - [7.1.3.5. Аргументы в пользу this(this)](#7-1-3-5-аргументы-в-пользу-this-this) + - [7.1.3.6. Уничтожение объекта и освобождение памяти](#7-1-3-6-уничтожение-объекта-и-освобождение-памяти) + - [7.1.3.7. Алгоритм уничтожения структуры](#7-1-3-7-алгоритм-уничтожения-структуры) + - [7.1.4. Статические конструкторы и деструкторы](#7-1-4-статические-конструкторы-и-деструкторы) + - [7.1.5. Методы](#7-1-5-методы) + - [7.1.5.1. Оператор присваивания](#7-1-5-1-оператор-присваивания) + - [7.1.5.2. Сравнение структур на равенство](#7-1-5-2-сравнение-структур-на-равенство) + - [7.1.6. Статические внутренние элементы](#7-1-6-статические-внутренние-элементы) + - [7.1.7. Спецификаторы доступа](#7-1-7-спецификаторы-доступа) + - [7.1.8. Вложенность структур и классов](#7-1-8-вложенность-структур-и-классов) + - [7.1.9. Структуры, вложенные в функции](#7-1-9-структуры-вложенные-в-функции) + - [7.1.10. Порождение подтипов в случае структур. Атрибут @disable](#7-1-10-порождение-подтипов-в-случае-структур-атрибут-disable) + - [7.1.11. Взаимное расположение полей. Выравнивание](#7-1-11-взаимное-расположение-полей-выравнивание) + - [7.1.11.1. Атрибут align](#7-1-11-1-атрибут-align) +- [7.2. Объединение](#7-2-объединение) +- [7.3. Перечисляемые значения](#7-3-перечисляемые-значения) + - [7.3.1. Перечисляемые типы](#7-3-1-перечисляемые-типы) + - [7.3.2. Свойства перечисляемых типов](#7-3-2-свойства-перечисляемых-типов) +- [7.4. alias](#7-4-alias) +- [7.5. Параметризированные контексты (конструкция template)](#75-параметризированные-контексты-конструкция-template) + - [7.5.1. Одноименные шаблоны](#7-5-1-одноименные-шаблоны) + - [7.5.2. Параметр шаблона this](#7-5-2-параметр-шаблона-this-4) +- [7.6. Инъекции кода с помощью конструкции mixin template](#7-6-инъекции-кода-с-помощью-конструкции-mixin-template) + - [7.6.1. Поиск идентификаторов внутри mixin](#7-6-1-поиск-идентификаторов-внутри-mixin) +- [7.7. Итоги](#7-7-итоги) Применяя классы, основные типы и функции, можно написать много хороших программ. С параметризированными классами и функциями дело идет еще лучше. Но нередко мы с сожалением отмечаем, что по нескольким причинам классы не представляют собой инструмент с максимальной абстракцией типа. @@ -83,11 +92,7 @@ struct Widget ### 7.1.1. Семантика копирования -Несколько заметных на глаз различий между структурами и классами -есть следствие менее очевидных семантических различий. Повторим -эксперимент, который мы уже проводили, обсуждая классы в разде- -ле 6.2. На этот раз создадим структуру и объект с одинаковыми поля -ми, а затем сравним поведение этих типов при копировании: +Несколько заметных на глаз различий между структурами и классами есть следствие менее очевидных семантических различий. Повторим эксперимент, который мы уже проводили, обсуждая классы в разделе 6.2. На этот раз создадим структуру и объект с одинаковыми полями, а затем сравним поведение этих типов при копировании: ```d class C @@ -115,28 +120,17 @@ unittest } ``` -При работе со структурами нет никаких ссылок, которые можно привя -зывать и перепривязывать с помощью операций инициализации и при -сваивания. Каждое имя экземпляра структуры связано с отдельным -значением. Как уже говорилось, объект-структура ведет себя *как значение*, а объект-класс – *как ссылка*. На рис. 7.1 показано положение дел -сразу после определения `c2` и `s2`. +При работе со структурами нет никаких ссылок, которые можно привязывать и перепривязывать с помощью операций инициализации и присваивания. Каждое имя экземпляра структуры связано с отдельным значением. Как уже говорилось, объект-структура ведет себя *как значение*, а объект-класс – *как ссылка*. На рис. 7.1 показано положение дел сразу после определения `c2` и `s2`. ![image-7-1-1](images/image-7-1-1.png) ***Рис. 7.1.*** *Инструкции `auto c2 = c1;` для объекта-класса `c1` и `auto s2 = s1;` для объекта-структуры `s1` действуют совершенно по-разному, поскольку класс по своей природе – ссылка, а структура – значение* -В отличие от имен `c1` и `с2`, допускающих привязку к любому объекту, -имена `s1` и `s2` прочно привязаны к реальным объектам. Нет способа за -ставить два имени ссылаться на один и тот же объект-структуру (кроме -ключевого слова `alias`, задающего простую эквивалентность имен; см. -раздел 7.4), и не бывает имени структуры без закрепленного за ним зна -чения, так что сравнение `s1 is null` бессмысленно и порождает ошибку -во время компиляции. +В отличие от имен `c1` и `с2`, допускающих привязку к любому объекту, имена `s1` и `s2` прочно привязаны к реальным объектам. Нет способа заставить два имени ссылаться на один и тот же объект-структуру (кроме ключевого слова `alias`, задающего простую эквивалентность имен; см. раздел 7.4), и не бывает имени структуры без закрепленного за ним значения, так что сравнение `s1 is null` бессмысленно и порождает ошибку во время компиляции. ### 7.1.2. Передача объекта-структуры в функцию -Поскольку объект типа `struct` ведет себя как значение, он и передается -в функцию по значению. +Поскольку объект типа `struct` ведет себя как значение, он и передается в функцию по значению. ```d struct S @@ -152,8 +146,7 @@ void fun(S s) } ``` -Передать объект-структуру по ссылке можно с помощью аргумента -с ключевым словом `ref` (см. раздел 5.2.1): +Передать объект-структуру по ссылке можно с помощью аргумента с ключевым словом `ref` (см. раздел 5.2.1): ```d void fun(ref S s) // fun получает ссылку @@ -162,16 +155,11 @@ void fun(ref S s) // fun получает ссылку } ``` -Раз уж мы заговорили о `ref`, отметим, что `this` передается по ссылке -внутрь методов структуры `S` в виде скрытого параметра `ref S`. +Раз уж мы заговорили о `ref`, отметим, что `this` передается по ссылке внутрь методов структуры `S` в виде скрытого параметра `ref S`. ### 7.1.3. Жизненный цикл объекта-структуры -В отличие от объектов-классов, объектам-структурам не свойственно -бесконечное время жизни (lifetime). Время жизни для них четко огра -ничено – так же как для временных (стековых) объектов функций. -Чтобы создать объект-структуру, задайте имя нужного типа, как если -бы вы вызывали функцию: +В отличие от объектов-классов, объектам-структурам не свойственно бесконечное время жизни (lifetime). Время жизни для них четко ограничено – так же как для временных (стековых) объектов функций. Чтобы создать объект-структуру, задайте имя нужного типа, как если бы вы вызывали функцию: ```d import std.math; @@ -190,13 +178,9 @@ unittest } ``` -Вызов `Test()` создает объект-структуру, все поля которого инициализи -рованы по умолчанию. В нашем случае это означает, что поле `t.a` при -нимает значение `0.4`, а `t.b` остается инициализированным значением -`double.init`. +Вызов `Test()` создает объект-структуру, все поля которого инициализированы по умолчанию. В нашем случае это означает, что поле `t.a` принимает значение `0.4`, а `t.b` остается инициализированным значением `double.init`. -Вызовы `Test(1)` и `Test(1.5, 2.5)` также разрешены и инициализируют по -ля объекта в порядке их объявления. Продолжим предыдущий пример: +Вызовы `Test(1)` и `Test(1.5, 2.5)` также разрешены и инициализируют поля объекта в порядке их объявления. Продолжим предыдущий пример: ```d unittest @@ -208,17 +192,11 @@ unittest } ``` -Поначалу может раздражать разница в синтаксисе выражения, создаю -щего объект-структуру `Test(‹аргументы›)`, и выражения, создающего объ -ект-класс `new Test(‹аргументы›)`. D мог бы отказаться от использования -ключевого слова new при создании объектов-классов, но это `new` напоми -нает программисту, что выполняется операция выделения памяти (то -есть необычное действие). +Поначалу может раздражать разница в синтаксисе выражения, создающего объект-структуру `Test(‹аргументы›)`, и выражения, создающего объект-класс `new Test(‹аргументы›)`. D мог бы отказаться от использования ключевого слова new при создании объектов-классов, но это `new` напоминает программисту, что выполняется операция выделения памяти (то есть необычное действие). #### 7.1.3.1. Конструкторы -Конструктор структуры определяется так же, как конструктор класса -(см. раздел 6.3.1): +Конструктор структуры определяется так же, как конструктор класса (см. раздел 6.3.1): ```d struct Test @@ -237,23 +215,19 @@ unittest } ``` -Присутствие хотя бы одного пользовательского конструктора блокирует -все упомянутые выше конструкторы, инициализирующие поля струк -туры: +Присутствие хотя бы одного пользовательского конструктора блокирует все упомянутые выше конструкторы, инициализирующие поля структуры: ```d auto t1 = Test(1.1, 1.2); // Ошибка! Нет конструктора, соответствующего вызову Test(double, double) ``` -Есть важное исключение: компилятор всегда определяет конструктор -без аргументов: +Есть важное исключение: компилятор всегда определяет конструктор без аргументов: ```d auto t2 = Test(); // Все в порядке, создается объект с "начинкой" по умолчанию ``` -Кроме того, пользовательский код не может определить собственный -конструктор без аргументов: +Кроме того, пользовательский код не может определить собственный конструктор без аргументов: ```d struct Test @@ -264,18 +238,11 @@ struct Test } ``` -Зачем нужно такое ограничение? Все из-за `T.init` – значения по умолча -нию, определяемого каждым типом. Оно должно быть статически из -вестно, что противоречит существованию конструктора по умолчанию, -выполняющего произвольный код. (Для классов `T.init` – это пустая -ссылка `null`, а не объект, построенный по умолчанию.) Правило для всех -структур: конструктор по умолчанию инициализирует все поля объек -та-структуры значениями по умолчанию. +Зачем нужно такое ограничение? Все из-за `T.init` – значения по умолчанию, определяемого каждым типом. Оно должно быть статически известно, что противоречит существованию конструктора по умолчанию, выполняющего произвольный код. (Для классов `T.init` – это пустая ссылка `null`, а не объект, построенный по умолчанию.) Правило для всех структур: конструктор по умолчанию инициализирует все поля объекта-структуры значениями по умолчанию. #### 7.1.3.2. Делегирование конструкторов -Скопируем пример из раздела 6.3.2 с заменой ключевого слова `class` на -`struct`: +Скопируем пример из раздела 6.3.2 с заменой ключевого слова `class` на `struct`: ```d struct Widget @@ -294,35 +261,20 @@ struct Widget } ``` -Код запускается, не требуя внесения каких-либо других изменений. -Так же как и классы, структуры позволяют одному конструктору деле -гировать построение объекта другому конструктору с теми же ограни -чениями. +Код запускается, не требуя внесения каких-либо других изменений. Так же как и классы, структуры позволяют одному конструктору делегировать построение объекта другому конструктору с теми же ограничениями. #### 7.1.3.3. Алгоритм построения -Классу приходится заботиться о выделении динамической памяти -и инициализации своего базового подобъекта (см. раздел 6.3.3). Со -структурами все гораздо проще, поскольку выделение памяти – явный -шаг алгоритма построения. Алгоритм построения объекта-структуры -типа `T` по шагам: +Классу приходится заботиться о выделении динамической памяти и инициализации своего базового подобъекта (см. раздел 6.3.3). Со структурами все гораздо проще, поскольку выделение памяти – явный шаг алгоритма построения. Алгоритм построения объекта-структуры типа `T` по шагам: -1. Скопировать значение `T.init` в память, где будет размещен объект, -путем копирования «сырой» памяти (а-ля `memcpy`). +1. Скопировать значение `T.init` в память, где будет размещен объект, путем копирования «сырой» памяти (а-ля `memcpy`). 2. Вызвать конструктор, если нужно. -Если инициализация некоторых или всех полей структуры выглядит -как `= void`, объем работ на первом шаге можно сократить, хотя и редко -намного, зато такой маневр часто порождает трудноуловимые ошибки -в вашем коде (тем не менее случай оправданного применения сокра -щенной инициализации иллюстрирует пример с классом `Transmogrifier` -в разделе 6.3.3). +Если инициализация некоторых или всех полей структуры выглядит как `= void`, объем работ на первом шаге можно сократить, хотя и редко намного, зато такой маневр часто порождает трудноуловимые ошибки в вашем коде (тем не менее случай оправданного применения сокращенной инициализации иллюстрирует пример с классом `Transmogrifier` в разделе 6.3.3). #### 7.1.3.4. Конструктор копирования this(this) -Предположим, требуется определить объект, который содержит ло -кальный (`private`) массив и предоставляет ограниченный API для ма -нипуляции этим массивом: +Предположим, требуется определить объект, который содержит локальный (`private`) массив и предоставляет ограниченный API для манипуляции этим массивом: ```d struct Widget @@ -343,9 +295,7 @@ struct Widget } ``` -У класса `Widget`, определенного таким образом, есть проблема: при ко -пировании объектов типа `Widget` между копиями создается отдаленная -зависимость. Судите сами: +У класса `Widget`, определенного таким образом, есть проблема: при копировании объектов типа `Widget` между копиями создается отдаленная зависимость. Судите сами: ```d unittest @@ -358,20 +308,9 @@ unittest } ``` -В чем проблема? Копирование содержимого `w1` в `w2` «поверхностно», то -есть оно выполняется поле за полем, без транзитивного копирования, -на какую бы память косвенно ни ссылалось каждое из полей. При ко -пировании массива память под новый массив не выделяется; копиру -ются лишь границы массива (см. раздел 4.1.4). После копирования `w1` -и `w2` действительно обладают различными полями с массивами, но ссы -лаются эти поля на одну и ту же область памяти. Такой объект, являю -щийся значением, но содержащий неявные разделяемые ссылки, мож -но в шутку назвать «клуктурой», то есть гибридом структуры (семанти -ка значения) и класса (семантика ссылки)[^2]. +В чем проблема? Копирование содержимого `w1` в `w2` «поверхностно», то есть оно выполняется поле за полем, без транзитивного копирования, на какую бы память косвенно ни ссылалось каждое из полей. При копировании массива память под новый массив не выделяется; копируются лишь границы массива (см. раздел 4.1.4). После копирования `w1` и `w2` действительно обладают различными полями с массивами, но ссылаются эти поля на одну и ту же область памяти. Такой объект, являющийся значением, но содержащий неявные разделяемые ссылки, можно в шутку назвать «клуктурой», то есть гибридом структуры (семантика значения) и класса (семантика ссылки)[^2]. -Обычно требуется, чтобы структура действительно вела себя как значе -ние, то есть чтобы копия становилась полностью независимой от своего -источника. Для этого определите конструктор копирования так: +Обычно требуется, чтобы структура действительно вела себя как значение, то есть чтобы копия становилась полностью независимой от своего источника. Для этого определите конструктор копирования так: ```d struct Widget @@ -392,27 +331,13 @@ struct Widget } ``` -Конструктор копирования вступает в силу во время копирования объ -екта. Чтобы инициализировать объект `приемник` с помощью объекта `источник` того же типа, компилятор должен выполнить следующие шаги: +Конструктор копирования вступает в силу во время копирования объекта. Чтобы инициализировать объект `приемник` с помощью объекта `источник` того же типа, компилятор должен выполнить следующие шаги: -1. Скопировать участок «сырой» памяти объекта `источник` в участок -«сырой» памяти объекта `приемник`. -2. Транзитивно для каждого поля, содержащего другие поля (то есть -поля, содержащего другое поле, содержащее третье поле, ...), для ко -торого определен метод `this(this)`, вызвать эти конструкторы снизу -вверх (начиная от наиболее глубоко вложенного поля). +1. Скопировать участок «сырой» памяти объекта `источник` в участок «сырой» памяти объекта `приемник`. +2. Транзитивно для каждого поля, содержащего другие поля (то есть поля, содержащего другое поле, содержащее третье поле, ...), для которого определен метод `this(this)`, вызвать эти конструкторы снизу вверх (начиная от наиболее глубоко вложенного поля). 3. Вызвать метод `this(this)` с объектом приемник. -Оригинальное название конструктора копирования «postblit construc -tor» происходит от «blit» – популярной аббревиатуры понятия «block -transfer», означавшего копирование «сырой» памяти. Язык применяет -«сырое» копирование при инициализации и разрешает сразу после это -го воспользоваться ловушкой. В предыдущем примере конструктор ко -пирования превращает только что полученный псевдоним массива в на -стоящую, полномасштабную копию, гарантируя, что с этого момента -между объектом-оригиналом и объектом-копией не будет ничего обще -го. Теперь, после добавления конструктора копирования, модуль легко -проходит этот тест: +Оригинальное название конструктора копирования «postblit constructor» происходит от «blit» – популярной аббревиатуры понятия «block transfer», означавшего копирование «сырой» памяти. Язык применяет «сырое» копирование при инициализации и разрешает сразу после этого воспользоваться ловушкой. В предыдущем примере конструктор копирования превращает только что полученный псевдоним массива в настоящую, полномасштабную копию, гарантируя, что с этого момента между объектом-оригиналом и объектом-копией не будет ничего общего. Теперь, после добавления конструктора копирования, модуль легко проходит этот тест: ```d unittest @@ -425,10 +350,7 @@ unittest } ``` -Вызов конструктора копирования вставляется в каждом случае копи -рования какого-либо объекта при явном или неявном создании новой -переменной. Например, при передаче объекта типа `Widget` по значению -в функцию также создается копия: +Вызов конструктора копирования вставляется в каждом случае копирования какого-либо объекта при явном или неявном создании новой переменной. Например, при передаче объекта типа `Widget` по значению в функцию также создается копия: ```d void fun(Widget w) // Передать по значению @@ -452,13 +374,7 @@ unittest } ``` -Второй шаг (часть с «транзитивным полем») процесса конструирования -при копировании заслуживает особого внимания. Основанием для та -кого поведения является *инкапсуляция*: конструктор копирования -объекта-структуры должен быть вызван даже тогда, когда эта структу -ра встроена в другую. Предположим, например, что мы решили сделать -`Widget` членом другой структуры, которая в свою очередь является чле -ном третьей структуры: +Второй шаг (часть с «транзитивным полем») процесса конструирования при копировании заслуживает особого внимания. Основанием для такого поведения является *инкапсуляция*: конструктор копирования объекта-структуры должен быть вызван даже тогда, когда эта структура встроена в другую. Предположим, например, что мы решили сделать `Widget` членом другой структуры, которая в свою очередь является членом третьей структуры: ```d struct Widget2 @@ -478,14 +394,7 @@ struct Widget3 } ``` -Теперь, если потребуется копировать объекты, содержащие другие объ -екты типа `Widget`, будет очень некстати, если компилятор забудет, как -нужно копировать подобъекты типа `Widget`. Вот почему при копирова -нии объектов типа `Widget2` инициируется вызов конструктора `this(this)` -для подобъекта `w1`, невзирая на то, что `Widget2` вообще об этом ничего не -знает. Кроме того, при копировании объектов типа `Widget3` конструктор -`this(this)` по-прежнему вызывается применительно к полю `w1` поля `w2`. -Внесем ясность: +Теперь, если потребуется копировать объекты, содержащие другие объекты типа `Widget`, будет очень некстати, если компилятор забудет, как нужно копировать подобъекты типа `Widget`. Вот почему при копировании объектов типа `Widget2` инициируется вызов конструктора `this(this)` для подобъекта `w1`, невзирая на то, что `Widget2` вообще об этом ничего не знает. Кроме того, при копировании объектов типа `Widget3` конструктор `this(this)` по-прежнему вызывается применительно к полю `w1` поля `w2`. Внесем ясность: ```d unittest @@ -501,17 +410,11 @@ unittest } ``` -Вкратце, если вы определите для некоторой структуры конструктор ко -пирования `this(this)`, компилятор позаботится о том, чтобы конструк -тор копирования вызывался в каждом случае копирования этого объ -екта-структуры независимо от того, является ли он самостоятельным -объектом или частью более крупного объекта-структуры. +Вкратце, если вы определите для некоторой структуры конструктор копирования `this(this)`, компилятор позаботится о том, чтобы конструктор копирования вызывался в каждом случае копирования этого объекта-структуры независимо от того, является ли он самостоятельным объектом или частью более крупного объекта-структуры. #### 7.1.3.5. Аргументы в пользу this(this) -Зачем был введен конструктор копирования? Ведь ничего подобного -в других языках пока нет. Почему бы просто не передавать исходный -объект в будущую копию (как это делает C++)? +Зачем был введен конструктор копирования? Ведь ничего подобного в других языках пока нет. Почему бы просто не передавать исходный объект в будущую копию (как это делает C++)? ```d // Это не D @@ -523,46 +426,11 @@ struct S } ``` -Опыт с C++ показал, что основная причина неэффективности программ -на C++ – злоупотребление копированием объектов. Чтобы сократить по -тери эффективности по этой причине, C++ устанавливает ряд случаев, -в которых компилятор может пропускать вызов конструктора копиро -вания (copy elision). Правила для этих случаев очень быстро усложни -лись, но все равно не охватывали все моменты, когда можно обойтись -без конструирования, то есть проблема осталась не решенной. Развива -ющийся стандарт C++ затрагивает эти вопросы, определяя новый тип -«ссылка на r-значение», позволяющий пользователю управлять пропус -ками вызова конструктора копирования, но плата за это – еще большее -усложнение языка. +Опыт с C++ показал, что основная причина неэффективности программ на C++ – злоупотребление копированием объектов. Чтобы сократить потери эффективности по этой причине, C++ устанавливает ряд случаев, в которых компилятор может пропускать вызов конструктора копирования (copy elision). Правила для этих случаев очень быстро усложнились, но все равно не охватывали все моменты, когда можно обойтись без конструирования, то есть проблема осталась не решенной. Развивающийся стандарт C++ затрагивает эти вопросы, определяя новый тип «ссылка на r-значение», позволяющий пользователю управлять пропусками вызова конструктора копирования, но плата за это – еще большее усложнение языка. -Благодаря конструктору копирования подход D становится простым -и во многом автоматизируемым. Начнем с того, что объекты в D долж -ны быть *перемещаемыми*, то есть не должны зависеть от своего располо -жения: копирование «сырой» памяти позволяет переместить объект -в другую область памяти, не нарушая его целостность. Тем не менее это -ограничение означает, что объект не может содержать так называемые -*внутренние указатели* – адреса подобъектов, являющихся его частя -ми. Без этой техники можно обойтись, так что D попросту ее исключает. -Создавать объекты с внутренними указателями в D запрещается, и ком -пилятор, как и подсистема времени исполнения, вправе предполагать, -что это правило соблюдается. Перемещаемые объекты открывают для -компилятора и подсистемы времени исполнения (например, для сбор -щика мусора) большие возможности, позволяющие программам стать -более быстрыми и компактными. +Благодаря конструктору копирования подход D становится простым и во многом автоматизируемым. Начнем с того, что объекты в D должны быть *перемещаемыми*, то есть не должны зависеть от своего расположения: копирование «сырой» памяти позволяет переместить объект в другую область памяти, не нарушая его целостность. Тем не менее это ограничение означает, что объект не может содержать так называемые *внутренние указатели* – адреса подобъектов, являющихся его частями. Без этой техники можно обойтись, так что D попросту ее исключает. Создавать объекты с внутренними указателями в D запрещается, и компилятор, как и подсистема времени исполнения, вправе предполагать, что это правило соблюдается. Перемещаемые объекты открывают для компилятора и подсистемы времени исполнения (например, для сборщика мусора) большие возможности, позволяющие программам стать более быстрыми и компактными. -Благодаря перемещаемости объектов копирование объектов становится -логическим продолжением перемещения объектов: конструктор копи -рования `this(this)` делает копирование объектов эквивалентом переме -щения с возможной последующей пользовательской обработкой. Таким -образом, пользовательский код не может изменить поля исходного объ -екта (что очень хорошо, поскольку копирование не должно затрагивать -объект-источник), но зато может корректировать поля, которые не долж -ны неявно разделять состояние с объектом-источником. Чтобы избежать -лишнего копирования, компилятор вправе по собственному усмотре -нию не вставлять вызов `this(this)`, если может доказать, что источник -копии не будет использован после завершения процесса копирования. -Рассмотрим, например, функцию, возвращающую объект типа `Widget` -(определенный выше) по значению: +Благодаря перемещаемости объектов копирование объектов становится логическим продолжением перемещения объектов: конструктор копирования `this(this)` делает копирование объектов эквивалентом перемещения с возможной последующей пользовательской обработкой. Таким образом, пользовательский код не может изменить поля исходного объекта (что очень хорошо, поскольку копирование не должно затрагивать объект-источник), но зато может корректировать поля, которые не должны неявно разделять состояние с объектом-источником. Чтобы избежать лишнего копирования, компилятор вправе по собственному усмотрению не вставлять вызов `this(this)`, если может доказать, что источник копии не будет использован после завершения процесса копирования. Рассмотрим, например, функцию, возвращающую объект типа `Widget` (определенный выше) по значению: ```d Widget hun(uint x) @@ -577,16 +445,7 @@ unittest } ``` -Наивный подход: просто создать объект типа `Widget` внутри функции -`hun`, а затем скопировать его в переменную `w`, применив побитовое копи -рование с последующим вызовом `this(this)`. Но это было бы слишком -расточительно: D полагается на перемещаемость объектов, так почему -бы попросту не переместить в переменную `w` уже отживший свое времен -ный объект, созданный функцией `hun`? Разницу никто не заметит, по -скольку после того, как функция `hun` вернет результат, временный объ -ект уже не нужен. Если в лесу упало дерево и никто этого не слышит, то -легче переместить его, чем копировать. Похожий (но не идентичный) -случай: +Наивный подход: просто создать объект типа `Widget` внутри функции `hun`, а затем скопировать его в переменную `w`, применив побитовое копирование с последующим вызовом `this(this)`. Но это было бы слишком расточительно: D полагается на перемещаемость объектов, так почему бы попросту не переместить в переменную `w` уже отживший свое временный объект, созданный функцией `hun`? Разницу никто не заметит, поскольку после того, как функция `hun` вернет результат, временный объект уже не нужен. Если в лесу упало дерево и никто этого не слышит, то легче переместить его, чем копировать. Похожий (но не идентичный) случай: ```d Widget iun(uint x) @@ -603,9 +462,7 @@ unittest } ``` -В этом случае переменная `result` тоже уходит в небытие сразу же после -того, как `iun` вернет управление, поэтому в вызове `this(this)` необходи -мости нет. Наконец, еще более тонкий случай: +В этом случае переменная `result` тоже уходит в небытие сразу же после того, как `iun` вернет управление, поэтому в вызове `this(this)` необходимости нет. Наконец, еще более тонкий случай: ```d void jun(Widget w) @@ -622,28 +479,15 @@ unittest } ``` -В этом случае сложнее выяснить, можно ли избавиться от вызова -`this(this)`. Вполне вероятно, что `‹код2›` продолжает использовать `w`, и то -гда перемещение этого значения из `unittest` в `jun` было бы некоррект -ным[^3]. +В этом случае сложнее выяснить, можно ли избавиться от вызова `this(this)`. Вполне вероятно, что `‹код2›` продолжает использовать `w`, и тогда перемещение этого значения из `unittest` в `jun` было бы некорректным[^3]. -Ввиду всех перечисленных соображений в D приняты следующие пра -вила пропуска вызова конструктора копирования: +Ввиду всех перечисленных соображений в D приняты следующие правила пропуска вызова конструктора копирования: -- Все анонимные r-значения перемещаются, а не копируются. Вызов -конструктора копирования `this(this)` всегда пропускается, если ори -гиналом является анонимное r-значение (то есть временный объект, -как в функции `hun` выше). -- В случае именованных временных объектов, которые создаются -внутри функции и располагаются в стеке, а затем возвращаются этой -функцией в качестве результата, вызов конструктора копирования -`this(this)` пропускается. -- Нет никаких гарантий, что компилятор воспользуется другими воз -можностями пропустить вызов конструктора копирования. +- Все анонимные r-значения перемещаются, а не копируются. Вызов конструктора копирования `this(this)` всегда пропускается, если оригиналом является анонимное r-значение (то есть временный объект, как в функции `hun` выше). +- В случае именованных временных объектов, которые создаются внутри функции и располагаются в стеке, а затем возвращаются этой функцией в качестве результата, вызов конструктора копирования `this(this)` пропускается. +- Нет никаких гарантий, что компилятор воспользуется другими возможностями пропустить вызов конструктора копирования. -Но иногда требуется предписать компилятору выполнить перемеще -ние. Фактически это выполняет функция `move` из модуля `std.algorithm` -стандартной библиотеки: +Но иногда требуется предписать компилятору выполнить перемещение. Фактически это выполняет функция `move` из модуля `std.algorithm` стандартной библиотеки: ```d import std.algorithm; @@ -664,12 +508,7 @@ unittest } ``` -Вызов функции `move` гарантирует, что `w` будет перемещена, а ее содержи -мое будет заменено пустым, сконструированным по умолчанию объек -том типа `Widget`. Кстати, это один из тех случаев, где пригодится неизме -няемый и не порождающий исключения конструктор по умолчанию -`Widget.init` (см. раздел 7.1.3.1). Без него сложно было бы найти способ ос -тавить источник перемещения в строго определенном пустом состоянии. +Вызов функции `move` гарантирует, что `w` будет перемещена, а ее содержимое будет заменено пустым, сконструированным по умолчанию объектом типа `Widget`. Кстати, это один из тех случаев, где пригодится неизменяемый и не порождающий исключения конструктор по умолчанию `Widget.init` (см. раздел 7.1.3.1). Без него сложно было бы найти способ оставить источник перемещения в строго определенном пустом состоянии. #### 7.1.3.6. Уничтожение объекта и освобождение памяти @@ -707,41 +546,22 @@ void main() Вне области видимости объекта. ``` -Каждая структура обладает *временем жизни в пределах области видимости* (*scoped lifetime*), то есть ее жизнь действительно заканчивается -с окончанием области видимости объекта. Подробнее: +Каждая структура обладает *временем жизни в пределах области видимости* (*scoped lifetime*), то есть ее жизнь действительно заканчивается с окончанием области видимости объекта. Подробнее: -- время жизни нестатического объекта, определенного внутри функ -ции, заканчивается в конце текущей области видимости (то есть -контекста) до уничтожения всех объектов-структур, определенных -перед ним; -- время жизни объекта, определенного в качестве члена другой струк -туры, заканчивается непосредственно после окончания времени жиз -ни включающего объекта; -- время жизни объекта, определенного в контексте модуля, бесконеч -но; если вам нужно вызвать деструктор этого объекта, сделайте это -в деструкторе модуля (см. раздел 11.3); -- время жизни объекта, определенного в качестве члена класса, за -канчивается в тот момент, когда сборщик мусора забирает память -включающего объекта. +- время жизни нестатического объекта, определенного внутри функции, заканчивается в конце текущей области видимости (то есть контекста) до уничтожения всех объектов-структур, определенных перед ним; +- время жизни объекта, определенного в качестве члена другой структуры, заканчивается непосредственно после окончания времени жизни включающего объекта; +- время жизни объекта, определенного в контексте модуля, бесконечно; если вам нужно вызвать деструктор этого объекта, сделайте это в деструкторе модуля (см. раздел 11.3); +- время жизни объекта, определенного в качестве члена класса, заканчивается в тот момент, когда сборщик мусора забирает память включающего объекта. -Язык гарантирует автоматический вызов деструктора `~this` по оконча -нии времени жизни объекта-структуры, что очень удобно, если вы хо -тите автоматически выполнять такие операции, как закрытие файлов -и освобождение всех важных ресурсов. +Язык гарантирует автоматический вызов деструктора `~this` по окончании времени жизни объекта-структуры, что очень удобно, если вы хотите автоматически выполнять такие операции, как закрытие файлов и освобождение всех важных ресурсов. -Оригинал копии, использующей конструктор копирования, подчиня -ется обычным правилам для времени жизни, но деструктор оригинала -копии, полученной перемещением «сырой» памяти без вызова `this(this)`, -не вызывается. +Оригинал копии, использующей конструктор копирования, подчиняется обычным правилам для времени жизни, но деструктор оригинала копии, полученной перемещением «сырой» памяти без вызова `this(this)`, не вызывается. -Освобождение памяти объекта-структуры по идее выполняется сразу -же после деструкции. +Освобождение памяти объекта-структуры по идее выполняется сразу же после деструкции. #### 7.1.3.7. Алгоритм уничтожения структуры -По умолчанию объекты-структуры уничтожаются в порядке, строго -обратном порядку их создания. То есть первым уничтожается объект- -структура, определенный в заданной области видимости последним: +По умолчанию объекты-структуры уничтожаются в порядке, строго обратном порядку их создания. То есть первым уничтожается объект-структура, определенный в заданной области видимости последним: ```d import std.conv, std.stdio; @@ -786,24 +606,13 @@ void main() первый объект уничтожен. ``` -Как и ожидалось, объект, созданный первым, был уничтожен послед -ним. На каждой итерации цикл входит в контекст и выходит из контек -ста управляемой инструкции. +Как и ожидалось, объект, созданный первым, был уничтожен последним. На каждой итерации цикл входит в контекст и выходит из контекста управляемой инструкции. -Можно явно инициировать вызов деструктора объекта-структуры с по -мощью инструкции `clear(объект);`. С функцией `clear` мы уже познакоми -лись в разделе 6.3.5. Тогда она оказалась полезной для уничтожения -состояния объекта-класса. Для объектов-структур функция `clear` дела -ет то же самое: вызывает деструктор, а затем копирует биты значения -`.init` в область памяти объекта. В результате получается правильно -сконструированный объект, правда, без какого-либо интересного содер -жания. +Можно явно инициировать вызов деструктора объекта-структуры с помощью инструкции `clear(объект);`. С функцией `clear` мы уже познакомились в разделе 6.3.5. Тогда она оказалась полезной для уничтожения состояния объекта-класса. Для объектов-структур функция `clear` делает то же самое: вызывает деструктор, а затем копирует биты значения `.init` в область памяти объекта. В результате получается правильно сконструированный объект, правда, без какого-либо интересного содержания. ### 7.1.4. Статические конструкторы и деструкторы -Структура может определять любое число статических конструкторов -и деструкторов. Это средство полностью идентично одноименному сред -ству для классов, с которым мы уже встречались в разделе 6.3.6. +Структура может определять любое число статических конструкторов и деструкторов. Это средство полностью идентично одноименному средству для классов, с которым мы уже встречались в разделе 6.3.6. ```d import std.stdio; @@ -837,13 +646,7 @@ void main() } ``` -Парность статических конструкторов и деструкторов не требуется. Под -система поддержки времени исполнения не делает ничего интересно -го – просто выполняет все статические конструкторы перед вычислени -ем функции `main` в порядке их определения. По завершении выполне -ния `main` подсистема поддержки времени исполнения так же скучно вы -зывает все статические деструкторы в порядке, обратном порядку их -определения. Предыдущая программа выведет на экран: +Парность статических конструкторов и деструкторов не требуется. Подсистема поддержки времени исполнения не делает ничего интересного – просто выполняет все статические конструкторы перед вычислением функции `main` в порядке их определения. По завершении выполнения `main` подсистема поддержки времени исполнения так же скучно вызывает все статические деструкторы в порядке, обратном порядку их определения. Предыдущая программа выведет на экран: ``` Первый статический конструктор @@ -853,24 +656,13 @@ void main() Первый статический деструктор ``` -Порядок выполнения очевиден для статических конструкторов и де -структоров, расположенных внутри одного модуля, но в случае не -скольких модулей не всегда все так же ясно. Порядок выполнения ста -тических конструкторов и деструкторов из разных модулей определен -в разделе 6.3.6. +Порядок выполнения очевиден для статических конструкторов и деструкторов, расположенных внутри одного модуля, но в случае нескольких модулей не всегда все так же ясно. Порядок выполнения статических конструкторов и деструкторов из разных модулей определен в разделе 6.3.6. ### 7.1.5. Методы -Структуры могут определять функции-члены, также называемые мето -дами. Поскольку в случае структур о наследовании и переопределении -речи нет, методы структур лишь немногим больше, чем функции. +Структуры могут определять функции-члены, также называемые методами. Поскольку в случае структур о наследовании и переопределении речи нет, методы структур лишь немногим больше, чем функции. -Нестатические методы структуры `S` принимают скрытый параметр `this` -по ссылке (эквивалент параметра `ref S`). Поиск имен внутри методов -структуры производится так же, как и внутри методов класса: парамет -ры перекрывают одноименные внутренние элементы структуры, а име -на внутренних элементов структуры перекрывают те же имена, объяв -ленные на уровне модуля. +Нестатические методы структуры `S` принимают скрытый параметр `this` по ссылке (эквивалент параметра `ref S`). Поиск имен внутри методов структуры производится так же, как и внутри методов класса: параметры перекрывают одноименные внутренние элементы структуры, а имена внутренних элементов структуры перекрывают те же имена, объявленные на уровне модуля. ```d void fun(int x) @@ -906,23 +698,9 @@ struct S } ``` -Кроме того, в этом примере есть тест модуля, определенный внутри -структуры. Такие тесты модуля, являющиеся «внутренними элемента -ми», не наделены никаким особым статусом, но их очень удобно встав -лять после каждого определения метода. Коду тела внутреннего теста -модуля доступна та же область видимости, что и обычным статическим -методам: например, тесту модуля в предыдущем примере не требуется -снабжать статическое поле y префиксом `S`, как это не потребовалось бы -любому методу структуры. +Кроме того, в этом примере есть тест модуля, определенный внутри структуры. Такие тесты модуля, являющиеся «внутренними элементами», не наделены никаким особым статусом, но их очень удобно вставлять после каждого определения метода. Коду тела внутреннего теста модуля доступна та же область видимости, что и обычным статическим методам: например, тесту модуля в предыдущем примере не требуется снабжать статическое поле y префиксом `S`, как это не потребовалось бы любому методу структуры. -Некоторые особые методы заслуживают более тщательного рассмотре -ния. К ним относятся оператор присваивания `opAssign`, используемый -оператором `=`, оператор равенства `opEquals`, используемый операторами -`==` и `!=`, а также упорядочивающий оператор `opCmp`, используемый опера -торами `<`, `<=`, `>=` и `>`. На самом деле, эта тема относится к главе 12, так как -затрагивает вопрос перегрузки операторов, но эти операторы особен -ные: компилятор может сгенерировать их автоматически, со всем их -особым поведением. +Некоторые особые методы заслуживают более тщательного рассмотрения. К ним относятся оператор присваивания `opAssign`, используемый оператором `=`, оператор равенства `opEquals`, используемый операторами `==` и `!=`, а также упорядочивающий оператор `opCmp`, используемый операторами `<`, `<=`, `>=` и `>`. На самом деле, эта тема относится к главе 12, так как затрагивает вопрос перегрузки операторов, но эти операторы особенные: компилятор может сгенерировать их автоматически, со всем их особым поведением. #### 7.1.5.1. Оператор присваивания @@ -935,23 +713,9 @@ Widget w1, w2; w1 = w2; ``` -то присваивание делается через копирование всех внутренних элемен -тов по очереди. В случае типа `Widget` такой подход может вызвать про -блемы, о которых говорилось в разделе 7.1.3.4. Если помните, структура -`Widget` обладает внутренним локальным массивом типа `int[]`, и планиро -валось, что он будет индивидуальным для каждого объекта типа `Widget`. -В ходе последовательного присваивания полей объекта `w2` объекту `w1` -поле `w2.array` будет присвоено полю `w1.array`, но это будет только простое -присваивание границ массива – в действительности, содержимое мас -сива скопировано не будет. Этот момент необходимо подкорректиро -вать, поскольку на самом деле мы хотим создать *дубликат* массива ори -гинальной структуры и присвоить его целевой структуре. +то присваивание делается через копирование всех внутренних элементов по очереди. В случае типа `Widget` такой подход может вызвать проблемы, о которых говорилось в разделе 7.1.3.4. Если помните, структура `Widget` обладает внутренним локальным массивом типа `int[]`, и планировалось, что он будет индивидуальным для каждого объекта типа `Widget`. В ходе последовательного присваивания полей объекта `w2` объекту `w1` поле `w2.array` будет присвоено полю `w1.array`, но это будет только простое присваивание границ массива – в действительности, содержимое массива скопировано не будет. Этот момент необходимо подкорректировать, поскольку на самом деле мы хотим создать *дубликат* массива оригинальной структуры и присвоить его целевой структуре. -Пользовательский код может перехватить присваивание, определив -метод `opAssign`. По сути, если `lhs` определяет `opAssign` с совместимой сиг -натурой, присваивание `lhs = rhs` транслируется в `lhs.opAssign(rhs)`, иначе -если `lhs` и `rhs` имеют один и тот же тип, выполняется обычное присваи -вание поле за полем. Давайте определим метод `Widget.opAssign`: +Пользовательский код может перехватить присваивание, определив метод `opAssign`. По сути, если `lhs` определяет `opAssign` с совместимой сигнатурой, присваивание `lhs = rhs` транслируется в `lhs.opAssign(rhs)`, иначе если `lhs` и `rhs` имеют один и тот же тип, выполняется обычное присваивание поле за полем. Давайте определим метод `Widget.opAssign`: ```d struct Widget @@ -966,9 +730,7 @@ struct Widget } ``` -Оператор присваивания возвращает ссылку на `this`, тем самым позво -ляя создавать цепочки присваиваний а-ля `w1 = w2 = w3`, которые компиля -тор заменяет на `w1.opAssign(w2.opAssign(w3))`. +Оператор присваивания возвращает ссылку на `this`, тем самым позволяя создавать цепочки присваиваний а-ля `w1 = w2 = w3`, которые компилятор заменяет на `w1.opAssign(w2.opAssign(w3))`. Осталась одна проблема. Рассмотрим присваивание: @@ -978,10 +740,7 @@ Widget w; w = Widget(50); // Ошибка! Невозможно привязать r-значение типа Widget к ссылке ref Widget! ``` -Проблема в том, что метод `opAssign` в таком виде, в каком он определен -сейчас, ожидает аргумент типа `ref Widget`, то есть l-значение типа `Widget`. -Чтобы помимо l-значений можно было бы присваивать еще и r-значе -ния, структура `Widget` должна определять *два* оператора присваивания: +Проблема в том, что метод `opAssign` в таком виде, в каком он определен сейчас, ожидает аргумент типа `ref Widget`, то есть l-значение типа `Widget`. Чтобы помимо l-значений можно было бы присваивать еще и r-значения, структура `Widget` должна определять *два* оператора присваивания: ```d import std.algorithm; @@ -1003,26 +762,9 @@ struct Widget } ``` -В версии метода, принимающей r-значения, уже отсутствует обраще -ние к свойству `.dup`. Почему? Ну, r-значение (а с ним и его массив) – это -практически собственность второго метода `opAssign`: оно было скопиро -вано перед входом в функцию и будет уничтожено сразу же после того, -как функция вернет управление. Это означает, что больше нет нужды -дублировать `rhs.array`, потому что его потерю никто не ощутит. Доста -точно лишь поменять местами `rhs.array` и `this.array`. Функция `opAssign` -возвращает результат, и `rhs` и старый массив объекта `this` уходят в ни -куда, а `this` остается с массивом, ранее принадлежавшим `rhs`, – совер -шенное сохранение состояния. +В версии метода, принимающей r-значения, уже отсутствует обращение к свойству `.dup`. Почему? Ну, r-значение (а с ним и его массив) – это практически собственность второго метода `opAssign`: оно было скопировано перед входом в функцию и будет уничтожено сразу же после того, как функция вернет управление. Это означает, что больше нет нужды дублировать `rhs.array`, потому что его потерю никто не ощутит. Достаточно лишь поменять местами `rhs.array` и `this.array`. Функция `opAssign` возвращает результат, и `rhs` и старый массив объекта `this` уходят в никуда, а `this` остается с массивом, ранее принадлежавшим `rhs`, – совершенное сохранение состояния. -Теперь можно совсем убрать первую перегруженную версию оператора -`opAssign`: та версия, что принимает `rhs` по значению, заботится обо всем -сама (l-значения автоматически конвертируются в r-значения). Но оста -вив версию с l-значением, мы сохраняем точку, через которую можно оп -тимизировать работу оператора присваивания. Вместо того чтобы дуб -лировать структуру-оригинал с помощью свойства `.dup`, метод `opAssign` -может проверять, достаточно ли в текущем массиве места для размеще -ния нового содержимого, и если да, то достаточно и записи поверх ста -рого массива на том же месте. +Теперь можно совсем убрать первую перегруженную версию оператора `opAssign`: та версия, что принимает `rhs` по значению, заботится обо всем сама (l-значения автоматически конвертируются в r-значения). Но оставив версию с l-значением, мы сохраняем точку, через которую можно оптимизировать работу оператора присваивания. Вместо того чтобы дублировать структуру-оригинал с помощью свойства `.dup`, метод `opAssign` может проверять, достаточно ли в текущем массиве места для размещения нового содержимого, и если да, то достаточно и записи поверх старого массива на том же месте. ```d // Внутри Widget ... @@ -1045,11 +787,7 @@ ref Widget opAssign(ref Widget rhs) #### 7.1.5.2. Сравнение структур на равенство -Средство для сравнения объектов-структур предоставляется «в комп -лекте» – это операторы `==` и `!=`. Сравнение представляет собой поочеред -ное сравнение внутренних элементов объектов и возвращает `false`, если -хотя бы два соответствующих друг другу элемента сравниваемых объ -ектов не равны, иначе результатом сравнения является `true`. +Средство для сравнения объектов-структур предоставляется «в комплекте» – это операторы `==` и `!=`. Сравнение представляет собой поочередное сравнение внутренних элементов объектов и возвращает `false`, если хотя бы два соответствующих друг другу элемента сравниваемых объектов не равны, иначе результатом сравнения является `true`. ```d struct Point @@ -1094,23 +832,9 @@ unittest } ``` -По сравнению с методом `opEquals` для классов (см. раздел 6.8.3) метод -`opEquals` для структур гораздо проще: ему не нужно беспокоиться о кор -ректности своих действий из-за наследования. Компилятор попросту -заменяет сравнение объектов-структур на вызов метода `opEquals`. Ко -нечно, применительно к структурам остается требование определять -осмысленный метод `opEquals`: рефлексивный, симметричный и транзи -тивный. Заметим, что хотя метод `Point.opEquals` выглядит довольно ос -мысленно, он не проходит тест на транзитивность. Лучшим вариантом -оператора сравнения на равенство было бы сравнение двух объектов ти -па `Point`, значения координат которых предварительно усечены до сво -их старших разрядов. Такую проверку было бы гораздо проще сделать -транзитивной. +По сравнению с методом `opEquals` для классов (см. раздел 6.8.3) метод `opEquals` для структур гораздо проще: ему не нужно беспокоиться о корректности своих действий из-за наследования. Компилятор попросту заменяет сравнение объектов-структур на вызов метода `opEquals`. Конечно, применительно к структурам остается требование определять осмысленный метод `opEquals`: рефлексивный, симметричный и транзитивный. Заметим, что хотя метод `Point.opEquals` выглядит довольно осмысленно, он не проходит тест на транзитивность. Лучшим вариантом оператора сравнения на равенство было бы сравнение двух объектов типа `Point`, значения координат которых предварительно усечены до своих старших разрядов. Такую проверку было бы гораздо проще сделать транзитивной. -Если структура содержит внутренние элементы, определяющие мето -ды `opEquals`, а сама такой метод не определяет, при сравнении все равно -будут вызваны существующие методы `opEquals` внутренних элементов. -Продолжим работать с примером, содержащим структуру `Point`: +Если структура содержит внутренние элементы, определяющие методы `opEquals`, а сама такой метод не определяет, при сравнении все равно будут вызваны существующие методы `opEquals` внутренних элементов. Продолжим работать с примером, содержащим структуру `Point`: ```d struct Rectangle @@ -1129,8 +853,7 @@ unittest } ``` -Для любых двух объектов `a` и `b` типа `Rectangle` вычисление `a == b` эквива -лентно вычислению выражения +Для любых двух объектов `a` и `b` типа `Rectangle` вычисление `a == b` эквивалентно вычислению выражения ```d a.leftBottom == b.leftBottom && a.rightTop == b.rightTop @@ -1142,25 +865,13 @@ a.leftBottom == b.leftBottom && a.rightTop == b.rightTop a.leftBottom.opEquals(b.leftBottom) && a.rightTop.opEquals(b.rightTop) ``` -Этот пример также показывает, что сравнение выполняется в порядке -объявления полей (т. е. поле `leftBottom` проверяется до проверки `rightTop`), и если встретились два неравных поля, сравнение завершается до -того, как будут проверены все поля, благодаря сокращенному вычисле -нию логических связок, построенных с помощью оператора `&&` (short -circuit evaluation). +Этот пример также показывает, что сравнение выполняется в порядке объявления полей (т. е. поле `leftBottom` проверяется до проверки `rightTop`), и если встретились два неравных поля, сравнение завершается до того, как будут проверены все поля, благодаря сокращенному вычислению логических связок, построенных с помощью оператора `&&` (short circuit evaluation). ### 7.1.6. Статические внутренние элементы -Структура может определять статические данные и статические внут -ренние функции. Помимо ограниченной видимости и подчинения пра -вилам доступа (см. раздел 7.1.7) режим работы статических внутренних -функций ничем не отличается от режима работы обычных функций. -Нет скрытого параметра `this`, не вовлечены никакие другие особые ме -ханизмы. +Структура может определять статические данные и статические внутренние функции. Помимо ограниченной видимости и подчинения правилам доступа (см. раздел 7.1.7) режим работы статических внутренних функций ничем не отличается от режима работы обычных функций. Нет скрытого параметра `this`, не вовлечены никакие другие особые механизмы. -Точно так же статические данные схожи с глобальными данными, -определенными на уровне модуля (см. раздел 5.2.4), во всем, кроме ви -димости и ограничений доступа, наложенных на эти статические дан -ные родительской структурой. +Точно так же статические данные схожи с глобальными данными, определенными на уровне модуля (см. раздел 5.2.4), во всем, кроме видимости и ограничений доступа, наложенных на эти статические данные родительской структурой. ```d import std.stdio; @@ -1204,14 +915,9 @@ void main() ### 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` применительно к структурам не имеет смысла, поскольку структуры не поддерживают наследование. -За подробной информацией обратитесь к соответствующим разделам. -А здесь мы лишь вкратце напомним смысл спецификаторов: +За подробной информацией обратитесь к соответствующим разделам. А здесь мы лишь вкратце напомним смысл спецификаторов: ```d struct S @@ -1223,16 +929,11 @@ struct S } ``` -Заметим, что хотя ключевое слово `export` разрешено везде, где синтак -сис допускает применение спецификатора доступа, семантика этого -ключевого слова зависит от реализации. +Заметим, что хотя ключевое слово `export` разрешено везде, где синтаксис допускает применение спецификатора доступа, семантика этого ключевого слова зависит от реализации. ### 7.1.8. Вложенность структур и классов -Часто бывает удобно вложить в структуру другую структуру или класс. -Например, контейнер дерева можно представить как оболочку-струк -туру с простым интерфейсом поиска, а внутри нее для определения уз -лов дерева использовать полиморфизм. +Часто бывает удобно вложить в структуру другую структуру или класс. Например, контейнер дерева можно представить как оболочку-структуру с простым интерфейсом поиска, а внутри нее для определения узлов дерева использовать полиморфизм. ```d struct Tree @@ -1296,27 +997,13 @@ class Window } ``` -В отличие от классов, вложенных в другие классы, вложенные струк -туры и классы, вложенные в другие структуры, не обладают никаким -скрытым внутренним элементом `outer` – никакой специальный код не -генерируется. Такие вложенные типы определяются в основном со -структурной целью – чтобы получить нужное управление доступом. +В отличие от классов, вложенных в другие классы, вложенные структуры и классы, вложенные в другие структуры, не обладают никаким скрытым внутренним элементом `outer` – никакой специальный код не генерируется. Такие вложенные типы определяются в основном со структурной целью – чтобы получить нужное управление доступом. ### 7.1.9. Структуры, вложенные в функции -Вспомним, что говорилось в разделе 6.11.1: вложенные классы находят -ся в привилегированном положении, ведь они обладают особыми, уни -кальными свойствами. Вложенному классу доступны параметры и ло -кальные переменные включающей функции. Если вы возвращаете вло -женный класс в качестве результата функции, компилятор даже разме -щает кадр функции в динамической памяти, чтобы параметры -и локальные переменные функции выжили после того, как она вернет -управление. +Вспомним, что говорилось в разделе 6.11.1: вложенные классы находятся в привилегированном положении, ведь они обладают особыми, уникальными свойствами. Вложенному классу доступны параметры и локальные переменные включающей функции. Если вы возвращаете вложенный класс в качестве результата функции, компилятор даже размещает кадр функции в динамической памяти, чтобы параметры и локальные переменные функции выжили после того, как она вернет управление. -Для единообразия и согласованности D оказывает структурам, вложен -ным в функции, те же услуги, что и классам, вложенным в функции. -Вложенная структура может обращаться к параметрам и локальным -переменным включающей функции: +Для единообразия и согласованности D оказывает структурам, вложенным в функции, те же услуги, что и классам, вложенным в функции. Вложенная структура может обращаться к параметрам и локальным переменным включающей функции: ```d void fun(int a) @@ -1344,33 +1031,13 @@ unittest } ``` -Во вложенные структуры встраивается волшебный «указатель на кадр», -с помощью которого они получают доступ к внешним значениям, та -ким как `a` и `b` в этом примере. Из-за этого дополнительного состояния -размер объекта `Local` не 4 байта, как можно было ожидать, а 8 (на 32-раз -рядной машине) – еще 4 байта занимает указатель на кадр. Если хотите -определить вложенную структуру без этого багажа, просто добавьте -в определение структуры `Local` ключевое слово `static` перед ключевым -словом `struct` – тем самым вы превратите `Local` в обычную структуру, то -есть закроете для нее доступ к `a` и `b`. +Во вложенные структуры встраивается волшебный «указатель на кадр», с помощью которого они получают доступ к внешним значениям, таким как `a` и `b` в этом примере. Из-за этого дополнительного состояния размер объекта `Local` не 4 байта, как можно было ожидать, а 8 (на 32-раз рядной машине) – еще 4 байта занимает указатель на кадр. Если хотите определить вложенную структуру без этого багажа, просто добавьте в определение структуры `Local` ключевое слово `static` перед ключевым словом `struct` – тем самым вы превратите `Local` в обычную структуру, то есть закроете для нее доступ к `a` и `b`. -Вложенные структуры практически бесполезны, разве что, по сравне -нию со вложенными классами, позволяют избежать беспричинного -ограничения. Функции не могут возвращать объекты вложенных -структур, так как вызывающему их коду недоступна информация о ти -пах таких объектов. Используя замысловатые вложенные структуры, -код неявно побуждает создавать все больше сложных функций, -а в идеале именно этого надо избегать в первую очередь. +Вложенные структуры практически бесполезны, разве что, по сравнению со вложенными классами, позволяют избежать беспричинного ограничения. Функции не могут возвращать объекты вложенных структур, так как вызывающему их коду недоступна информация о типах таких объектов. Используя замысловатые вложенные структуры, код неявно побуждает создавать все больше сложных функций, а в идеале именно этого надо избегать в первую очередь. ### 7.1.10. Порождение подтипов в случае структур. Атрибут @disable -К структурам неприменимы наследование и полиморфизм, но этот тип -данных по-прежнему поддерживает конструкцию `alias this`, впервые -представленную в разделе 6.13. С помощью `alias this` можно сделать -структуру подтипом любого другого типа. Определим, к примеру, про -стой тип `Final`, поведением очень напоминающий ссылку на класс – во -всем, кроме того что переменную типа `Final` невозможно перепривязать! -Пример использования переменной `Final`: +К структурам неприменимы наследование и полиморфизм, но этот тип данных по-прежнему поддерживает конструкцию `alias this`, впервые представленную в разделе 6.13. С помощью `alias this` можно сделать структуру подтипом любого другого типа. Определим, к примеру, простой тип `Final`, поведением очень напоминающий ссылку на класс – во всем, кроме того что переменную типа `Final` невозможно перепривязать! Пример использования переменной `Final`: ```d import std.stdio; @@ -1393,14 +1060,9 @@ unittest } ``` -Предназначение типа `Final` – быть особым видом ссылки на класс, раз -и навсегда привязанной к одному объекту. Такие «преданные» ссылки -полезны для реализации множества проектных идей. +Предназначение типа `Final` – быть особым видом ссылки на класс, раз и навсегда привязанной к одному объекту. Такие «преданные» ссылки полезны для реализации множества проектных идей. -Первый шаг – избавиться от присваивания. Проблема в том, что опера -тор присваивания генерируется автоматически, если не объявлен поль -зователем, поэтому структура `Final` должна вежливо указать компиля -тору не делать этого. Для этого предназначен атрибут `@disable`: +Первый шаг – избавиться от присваивания. Проблема в том, что оператор присваивания генерируется автоматически, если не объявлен пользователем, поэтому структура `Final` должна вежливо указать компилятору не делать этого. Для этого предназначен атрибут `@disable`: ```d struct Final(T) @@ -1411,13 +1073,9 @@ struct Final(T) } ``` -С помощью атрибута `@disable` можно запретить и другие сгенерирован -ные функции, например сравнение. +С помощью атрибута `@disable` можно запретить и другие сгенерированные функции, например сравнение. -До сих пор все шло хорошо. Чтобы реализовать `Final!T`, нужно с помо -щью конструкции `alias this` сделать `Final(T)` подтипом `T`, но чтобы при -этом полученный тип не являлся l-значением. Ошибочное решение вы -глядит так: +До сих пор все шло хорошо. Чтобы реализовать `Final!T`, нужно с помощью конструкции `alias this` сделать `Final(T)` подтипом `T`, но чтобы при этом полученный тип не являлся l-значением. Ошибочное решение выглядит так: ```d // Ошибочное решение @@ -1435,15 +1093,9 @@ struct Final(T) } ``` -Структура `Final` хранит ссылку на себя в поле `payload`, которое инициа -лизируется в конструкторе. Кроме того, объявив, но не определяя ме -тод `opAssign`, структура эффективно «замораживает» присваивание. Та -ким образом, клиентский код, пытающийся присвоить значение объек -ту типа `Final!T`, или не сможет обратиться к `payload` (из-за `private`), или -получит ошибку во время компоновки. +Структура `Final` хранит ссылку на себя в поле `payload`, которое инициализируется в конструкторе. Кроме того, объявив, но не определяя метод `opAssign`, структура эффективно «замораживает» присваивание. Таким образом, клиентский код, пытающийся присвоить значение объекту типа `Final!T`, или не сможет обратиться к `payload` (из-за `private`), или получит ошибку во время компоновки. -Ошибка `Final` – в использовании инструкции `alias payload this;`. Этот -тест модуля делает что-то непредусмотренное: +Ошибка `Final` – в использовании инструкции `alias payload this;`. Этот тест модуля делает что-то непредусмотренное: ```d class A @@ -1464,20 +1116,9 @@ unittest } ``` -`alias payload this` действует довольно просто: каждый раз, когда значе -ние `объект` типа `Final!T` используется в недопустимом для этого типа кон -тексте, компилятор вместо `объект` пишет `объект.payload` (то есть делает -`объект.payload` *псевдонимом* для `объекта` в соответствии с именем и син -таксисом конструкции `alias`). Но выражение `объект.payload` представля -ет собой непосредственное обращение к полю `объект`, следовательно, яв -ляется l-значением. Это l-значение привязано к переданному по ссылке -параметру функции `sneaky` и, таким образом, позволяет `sneaky` напря -мую изменять значение поля объекта `v`. +`alias payload this` действует довольно просто: каждый раз, когда значение `объект` типа `Final!T` используется в недопустимом для этого типа контексте, компилятор вместо `объект` пишет `объект.payload` (то есть делает `объект.payload` *псевдонимом* для `объекта` в соответствии с именем и синтаксисом конструкции `alias`). Но выражение `объект.payload` представляет собой непосредственное обращение к полю `объект`, следовательно, является l-значением. Это l-значение привязано к переданному по ссылке параметру функции `sneaky` и, таким образом, позволяет `sneaky` напрямую изменять значение поля объекта `v`. -Чтобы это исправить, нужно сделать объект псевдонимом r-значения. -Так мы получим полную функциональность, но ссылка, сохраненная -в `payload`, станет неприкосновенной. Очень просто осуществить привяз -ку к r-значению с помощью свойства (объявленного с атрибутом `@property`), возвращающего `payload` по значению: +Чтобы это исправить, нужно сделать объект псевдонимом r-значения. Так мы получим полную функциональность, но ссылка, сохраненная в `payload`, станет неприкосновенной. Очень просто осуществить привязку к r-значению с помощью свойства (объявленного с атрибутом `@property`), возвращающего `payload` по значению: ```d struct Final(T) @@ -1495,27 +1136,9 @@ struct Final(T) } ``` -Ключевой момент в новом определении структуры – то, что метод `get` -возвращает значение типа `T`, а не `ref T`. Конечно, объект, на который ссы -лается `payload`, изменить можно (если хотите избежать этого, ознакомь -тесь с квалификаторами `const` и `immutable`; см. главу 8). Но структура -`Final` свои обязательства теперь выполняет. Во-первых, для любого ти -па класса `T` справедливо, что `Final!T` ведет себя как `T`. Во-вторых, однаж -ды привязав переменную типа `Final!T` к некоторому объекту с помощью -конструктора, вы не сможете ее перепривязать ни к какому другому -объекту. В частности, тест модуля, из-за которого пришлось отказаться -от предыдущего определения `Final`, больше не компилируется, посколь -ку вызов `sneaky(v)` теперь некорректен: r-значение типа `A` (неявно полу -ченное из `v` с помощью `v.get`) не может быть привязано к `ref A`, как тре -буется функции `sneaky` для ее черных дел. +Ключевой момент в новом определении структуры – то, что метод `get` возвращает значение типа `T`, а не `ref T`. Конечно, объект, на который ссылается `payload`, изменить можно (если хотите избежать этого, ознакомьтесь с квалификаторами `const` и `immutable`; см. главу 8). Но структура `Final` свои обязательства теперь выполняет. Во-первых, для любого типа класса `T` справедливо, что `Final!T` ведет себя как `T`. Во-вторых, однажды привязав переменную типа `Final!T` к некоторому объекту с помощью конструктора, вы не сможете ее перепривязать ни к какому другому объекту. В частности, тест модуля, из-за которого пришлось отказаться от предыдущего определения `Final`, больше не компилируется, поскольку вызов `sneaky(v)` теперь некорректен: r-значение типа `A` (неявно полученное из `v` с помощью `v.get`) не может быть привязано к `ref A`, как требуется функции `sneaky` для ее черных дел. -В нашей бочке меда осталась только одна ложка дегтя (на самом деле, -всего лишь чайная ложечка), от которой надо избавиться. Всякий раз, -когда тип, подобный `Final`, использует конструкцию `alias get this`, необ -ходимо уделять особое внимание собственным идентификаторам `Final`, -перекрывающим одноименные идентификаторы, определенные в типе, -псевдонимом которого становится `Final`. Предположим, мы используем -тип `Final!Widget`, а класс `Widget` и сам определяет свойство `get`: +В нашей бочке меда осталась только одна ложка дегтя (на самом деле, всего лишь чайная ложечка), от которой надо избавиться. Всякий раз, когда тип, подобный `Final`, использует конструкцию `alias get this`, необходимо уделять особое внимание собственным идентификаторам `Final`, перекрывающим одноименные идентификаторы, определенные в типе, псевдонимом которого становится `Final`. Предположим, мы используем тип `Final!Widget`, а класс `Widget` и сам определяет свойство `get`: ```d class Widget @@ -1531,9 +1154,7 @@ unittest } ``` -Чтобы избежать таких коллизий, воспользуемся соглашением об име -новании. Для надежности будем просто добавлять к именам видимых -свойств имя соответствующего типа: +Чтобы избежать таких коллизий, воспользуемся соглашением об именовании. Для надежности будем просто добавлять к именам видимых свойств имя соответствующего типа: ```d struct Final(T) @@ -1551,17 +1172,11 @@ struct Final(T) } ``` -Соблюдение такого соглашения сводит к минимуму риск непредвиден -ных коллизий. (Конечно, иногда можно намеренно перехватывать не -которые методы, оставив вызовы к ним за перехватчиком.) +Соблюдение такого соглашения сводит к минимуму риск непредвиденных коллизий. (Конечно, иногда можно намеренно перехватывать некоторые методы, оставив вызовы к ним за перехватчиком.) #### 7.1.11. Взаимное расположение полей. Выравнивание -Как располагаются поля в объекте-структуре? D очень консервативен -в отношении структур: он располагает элементы их содержимого в том -же порядке, в каком они указаны в определении структуры, но сохра -няет за собой право вставлять между полями *отступы* (*padding*). Рас -смотрим пример: +Как располагаются поля в объекте-структуре? D очень консервативен в отношении структур: он располагает элементы их содержимого в том же порядке, в каком они указаны в определении структуры, но сохраняет за собой право вставлять между полями *отступы* (*padding*). Рассмотрим пример: ```d struct A @@ -1572,51 +1187,17 @@ struct A } ``` -Если бы компилятор располагал поля в точном соответствии с размера -ми, указанными в структуре `A`, то адресом поля `b` оказался бы адрес -объекта `A` плюс 1 (поскольку поле `a` типа `char` занимает ровно 1 байт). Но -такое расположение проблематично, ведь современные компьютерные -системы извлекают данные только блоками по 4 или 8 байт, то есть мо -гут извлекать только данные, расположенные по адресам, кратным 4 -и 8 соответственно. Предположим, объект типа `A` расположен по «хоро -шему» адресу, например кратному 8. Тогда адрес поля `b` точно окажется -не в лучшем районе города. Чтобы извлечь `b`, процессору придется пово -зиться, ведь нужно будет «склеивать» значение `b`, собирая его из кусоч -ков размером в байт. Усугубляет ситуацию то, что в зависимости от -компилятора и низкоуровневой архитектуры аппаратного обеспечения -эта операция сборки может быть выполнена лишь в ответ на прерыва -ние ядра «обращение к невыровненным данным», обработка которого -требует своих (и немалых) накладных расходов. А это вам не семеч -ки щелкать: такая дополнительная гимнастика легко снижает скорость -доступа на несколько порядков. +Если бы компилятор располагал поля в точном соответствии с размерами, указанными в структуре `A`, то адресом поля `b` оказался бы адрес объекта `A` плюс 1 (поскольку поле `a` типа `char` занимает ровно 1 байт). Но такое расположение проблематично, ведь современные компьютерные системы извлекают данные только блоками по 4 или 8 байт, то есть могут извлекать только данные, расположенные по адресам, кратным 4 и 8 соответственно. Предположим, объект типа `A` расположен по «хорошему» адресу, например кратному 8. Тогда адрес поля `b` точно окажется не в лучшем районе города. Чтобы извлечь `b`, процессору придется повозиться, ведь нужно будет «склеивать» значение `b`, собирая его из кусочков размером в байт. Усугубляет ситуацию то, что в зависимости от компилятора и низкоуровневой архитектуры аппаратного обеспечения эта операция сборки может быть выполнена лишь в ответ на прерывание ядра «обращение к невыровненным данным», обработка которого требует своих (и немалых) накладных расходов. А это вам не семечки щелкать: такая дополнительная гимнастика легко снижает скорость доступа на несколько порядков. -Вот почему современные компиляторы располагают данные в памяти -с *отступами*. Компилятор вставляет в объект дополнительные байты, -чтобы обеспечить расположение всех полей с удобными смещениями. -Таким образом, выделение под объекты областей памяти с адресами, -кратными слову, гарантирует быстрый доступ ко всем внутренним эле -ментам этих объектов. На рис. 7.2 показано расположение полей типа `A` -по схеме с отступами. +Вот почему современные компиляторы располагают данные в памяти с *отступами*. Компилятор вставляет в объект дополнительные байты, чтобы обеспечить расположение всех полей с удобными смещениями. Таким образом, выделение под объекты областей памяти с адресами, кратными слову, гарантирует быстрый доступ ко всем внутренним элементам этих объектов. На рис. 7.2 показано расположение полей типа `A` по схеме с отступами. ![image-7-1-11](images/image-7-1-11.png) ***Рис. 7.2.*** *Расположение полей типа `A` по схеме с отступами. Заштрихованные области – это отступы, вставленные для правильного выравнивания. Компилятор вставляет в объект две лакуны, тем самым добавляя 6 байт простаивающего места или 50% общего размера объекта* -Полученное расположение полей характеризуется обилием отступов (за -штрихованных областей). В случае классов компилятор волен упорядо -чивать поля по собственному усмотрению, но при работе со структурой -есть смысл позаботиться о расположении данных, если объем исполь -зуемой памяти имеет значение. Лучше всего расположить поле типа int -первым, а после него – два поля типа `char`. При таком порядке полей -структура займет 64 бита, включая 2 байта отступа. +Полученное расположение полей характеризуется обилием отступов (заштрихованных областей). В случае классов компилятор волен упорядочивать поля по собственному усмотрению, но при работе со структурой есть смысл позаботиться о расположении данных, если объем используемой памяти имеет значение. Лучше всего расположить поле типа `int` первым, а после него – два поля типа `char`. При таком порядке полей структура займет 64 бита, включая 2 байта отступа. -Каждое из полей объекта обладает известным во время компиляции сме -щением относительно начального адреса объекта. Это смещение всегда -одинаково для всех объектов заданного типа в рамках одной программы -(оно может меняться от компиляции к компиляции, но не от запуска -к запуску). Смещение доступно пользовательскому коду как значение -свойства `.offsetof`, неявно определенного для каждого поля класса или -структуры: +Каждое из полей объекта обладает известным во время компиляции смещением относительно начального адреса объекта. Это смещение всегда одинаково для всех объектов заданного типа в рамках одной программы (оно может меняться от компиляции к компиляции, но не от запуска к запуску). Смещение доступно пользовательскому коду как значение свойства `.offsetof`, неявно определенного для каждого поля класса или структуры: ```d import std.stdio; @@ -1635,13 +1216,7 @@ void main() } ``` -Эталонная реализация компилятора выведет `0 4 8`, открывая схему рас -положения полей, которую мы уже видели на рис. 7.2. Не совсем удоб -но, что для доступа к некоторой статической информации о типе `A` при -ходится создавать объект этого типа, но синтаксис `A.a.offsetof` не ком -пилируется. Здесь поможет такой трюк: выражение `A.init.a.offsetof` -позволяет получить смещение для любого внутреннего элемента струк -туры в виде константы, известной во время компиляции. +Эталонная реализация компилятора выведет `0 4 8`, открывая схему расположения полей, которую мы уже видели на рис. 7.2. Не совсем удобно, что для доступа к некоторой статической информации о типе `A` приходится создавать объект этого типа, но синтаксис `A.a.offsetof` не компилируется. Здесь поможет такой трюк: выражение `A.init.a.offsetof` позволяет получить смещение для любого внутреннего элемента структуры в виде константы, известной во время компиляции. ```d import std.stdio; @@ -1661,16 +1236,11 @@ void main() } ``` -D гарантирует, что все байты отступов последовательно заполняются -нулями. +D гарантирует, что все байты отступов последовательно заполняются нулями. #### 7.1.11.1. Атрибут align -Чтобы перекрыть выбор компилятора, определив собственное выравни -вание, что повлияет на вставляемые отступы, объявляйте поля с атрибу -том `align`. Такое переопределение может понадобиться для взаимодейст -вия с определенной аппаратурой или для работы по бинарному протоко -лу, задающему особое выравнивание. Пример атрибута `align` в действии: +Чтобы перекрыть выбор компилятора, определив собственное выравнивание, что повлияет на вставляемые отступы, объявляйте поля с атрибутом `align`. Такое переопределение может понадобиться для взаимодействия с определенной аппаратурой или для работы по бинарному протоколу, задающему особое выравнивание. Пример атрибута `align` в действии: ```d class A @@ -1681,14 +1251,7 @@ class A } ``` -При таком определении поля структуры `A` располагаются без пустот -между ними. (В конце объекта при этом может оставаться зарезервиро -ванное, но не занятое место.) Аргумент атрибута `align` означает *максимальное* выравнивание поля, но реальное выравнивание не может пре -высить естественное выравнивание для типа этого поля. Получить ес -тественное выравнивание типа `T` позволяет определенное компилятором -свойство `T.alignof`. Если вы, например, укажете для `b` выравнивание -align(200) вместо указанного в примере `align(1)`, то реально выравнива -ние примет значение `4`, равное `int.alignof`. +При таком определении поля структуры `A` располагаются без пустот между ними. (В конце объекта при этом может оставаться зарезервированное, но не занятое место.) Аргумент атрибута `align` означает *максимальное* выравнивание поля, но реальное выравнивание не может превысить естественное выравнивание для типа этого поля. Получить естественное выравнивание типа `T` позволяет определенное компилятором свойство `T.alignof`. Если вы, например, укажете для `b` выравнивание `align(200)` вместо указанного в примере `align(1)`, то реально выравнивание примет значение `4`, равное `int.alignof`. Атрибут `align` можно применять к целому классу или структуре: @@ -1701,20 +1264,9 @@ align(1) struct A } ``` -Для структуры атрибут `align` устанавливает выравнивание по умолча -нию заданным значением. Это умолчание можно переопределить инди -видуа льными атрибутами `align` внутри структуры. Если для поля ти -па `T` указать только ключевое слово `align` без числа, компилятор прочи -тает это как `align(T.alignof)`, то есть такая запись переустанавливает -выравнивание поля в его естественное значение. +Для структуры атрибут `align` устанавливает выравнивание по умолчанию заданным значением. Это умолчание можно переопределить индивидуа льными атрибутами `align` внутри структуры. Если для поля типа `T` указать только ключевое слово `align` без числа, компилятор прочитает это как `align(T.alignof)`, то есть такая запись переустанавливает выравнивание поля в его естественное значение. -Атрибут `align` не предназначен для использования с указателями и ссыл -ками. Сборщик мусора действует из расчета, что все ссылки и указатели -выровнены по размеру типа `size_t`. Компилятор не настаивает на со -блюдении этого ограничения, поскольку в общем случае у вас могут -быть указатели и ссылки, не контролируемые сборщиком мусора. Та -ким образом, следующее определение крайне опасно, поскольку компи -лируется без предупреждений: +Атрибут `align` не предназначен для использования с указателями и ссылками. Сборщик мусора действует из расчета, что все ссылки и указатели выровнены по размеру типа `size_t`. Компилятор не настаивает на соблюдении этого ограничения, поскольку в общем случае у вас могут быть указатели и ссылки, не контролируемые сборщиком мусора. Таким образом, следующее определение крайне опасно, поскольку компилируется без предупреждений: ```d struct Node @@ -1724,24 +1276,13 @@ struct Node } ``` -Если этот код выполнит присваивание `объект.next = new Node` (то есть за -полнит `объект.next` ссылкой, контролируемой сборщиком мусора), хаос -обеспечен: неверно выровненная ссылка пропадает из поля зрения сбор -щика мусора, память может быть освобождена, и `объект.next` превраща -ется в «висячий» указатель. +Если этот код выполнит присваивание `объект.next = new Node` (то есть заполнит `объект.next` ссылкой, контролируемой сборщиком мусора), хаос обеспечен: неверно выровненная ссылка пропадает из поля зрения сборщика мусора, память может быть освобождена, и `объект.next` превращается в «висячий» указатель. ## 7.2. Объединение -Объединения в стиле C можно использовать и в D, но не забывайте, что -делать это нужно редко и с особой осторожностью. +Объединения в стиле C можно использовать и в D, но не забывайте, что делать это нужно редко и с особой осторожностью. -Объединение (`union`) – это что-то вроде структуры, все внутренние поля -которой начинаются по одному и тому же адресу. Таким образом, их об -ласти памяти перекрываются, а это значит, что именно вы как пользо -ватель объединения отвечаете за соответствие записываемой и считы -ваемой информации: нужно всегда читать в точности тот тип, который -был записан. В любой конкретный момент времени только один внут -ренний элемент объединения обладает корректным значением. +Объединение (`union`) – это что-то вроде структуры, все внутренние поля которой начинаются по одному и тому же адресу. Таким образом, их области памяти перекрываются, а это значит, что именно вы как пользователь объединения отвечаете за соответствие записываемой и считываемой информации: нужно всегда читать в точности тот тип, который был записан. В любой конкретный момент времени только один внутренний элемент объединения обладает корректным значением. ```d union IntOrFloat @@ -1762,27 +1303,11 @@ unittest } ``` -Поскольку типы `int` и `float` имеют строго один и тот же размер (4 байта), -внутри объединения `IntOrFloat` их области памяти в точности совпадают. -Но детали их расположения не регламентированы, например, пред -ставления `_int` и `_float` могут отличаться порядком хранения байтов: -старший байт `_int` может иметь наименьший адрес, а старший байт -`_float` (тот, что содержит знак и большую часть показателя степени) – -наибольший адрес. +Поскольку типы `int` и `float` имеют строго один и тот же размер (4 байта), внутри объединения `IntOrFloat` их области памяти в точности совпадают. Но детали их расположения не регламентированы, например, представления `_int` и `_float` могут отличаться порядком хранения байтов: старший байт `_int` может иметь наименьший адрес, а старший байт `_float` (тот, что содержит знак и большую часть показателя степени) – наибольший адрес. -Объединения не помечаются, то есть сам объект типа `union` не содержит -«метки», которая служила бы средством, позволяющим определять, -какой из внутренних элементов является «хорошим». Ответственность -за корректное использование объединения целиком ложится на плечи -пользователя, что делает объединения довольно неприятным средством -при построении более крупных абстракций. +Объединения не помечаются, то есть сам объект типа `union` не содержит «метки», которая служила бы средством, позволяющим определять, какой из внутренних элементов является «хорошим». Ответственность за корректное использование объединения целиком ложится на плечи пользователя, что делает объединения довольно неприятным средством при построении более крупных абстракций. -В определенном, но неинициализированном объекте типа `union` уже -есть одно инициализированное поле: первое поле автоматически ини -циализируется соответствующим значением `.init`, поэтому оно доступ -но для чтения сразу по завершении построения по умолчанию. Чтобы -инициализировать первое поле значением, отличным от `.init`, укажите -нужное инициализирующее выражение в фигурных скобках: +В определенном, но неинициализированном объекте типа `union` уже есть одно инициализированное поле: первое поле автоматически инициализируется соответствующим значением `.init`, поэтому оно доступно для чтения сразу по завершении построения по умолчанию. Чтобы инициализировать первое поле значением, отличным от `.init`, укажите нужное инициализирующее выражение в фигурных скобках: ```d unittest @@ -1792,8 +1317,7 @@ unittest } ``` -В статическом объекте типа `union` может быть инициализировано и дру -гое поле. Для этого используйте следующий синтаксис: +В статическом объекте типа `union` может быть инициализировано и другое поле. Для этого используйте следующий синтаксис: ```d unittest @@ -1803,13 +1327,7 @@ unittest } ``` -Следует отметить, что нередко объединение служит именно для того, -чтобы считывать тип, отличный от исходно записанного, – в соответст -вии с порядком управления представлением, принятым в некоторой -системе. По этой причине компилятор не выявляет даже те случаи не -корректного использования объединений, которые может обнаружить. -Например, на 32-разрядной машине Intel следующий код компилиру -ется и даже выполнение инструкции `assert` не порождает исключений: +Следует отметить, что нередко объединение служит именно для того, чтобы считывать тип, отличный от исходно записанного, – в соответствии с порядком управления представлением, принятым в некоторой системе. По этой причине компилятор не выявляет даже те случаи некорректного использования объединений, которые может обнаружить. Например, на 32-разрядной машине Intel следующий код компилируется и даже выполнение инструкции `assert` не порождает исключений: ``` unittest @@ -1820,12 +1338,9 @@ unittest } ``` -Объединение может определять функции-члены и, в общем случае, лю -бые из тех внутренних элементов, которые может определять структу -ра, за исключением конструкторов и деструкторов. +Объединение может определять функции-члены и, в общем случае, любые из тех внутренних элементов, которые может определять структура, за исключением конструкторов и деструкторов. -Чаще всего (точнее, наименее редко) объединения используются в каче -стве анонимных членов структур. Например: +Чаще всего (точнее, наименее редко) объединения используются в качестве анонимных членов структур. Например: ```d import std.contracts; @@ -1865,52 +1380,17 @@ unittest (Подробно тип `enum` описан в разделе 7.3.) -Этот пример демонстрирует чисто классический способ использования -`union` в качестве вспомогательного средства для определения так назы -ваемого *размеченного объединения* (*discriminated union*, *tagged union*), -также известного как алгебраический тип. Размеченное объединение -инкапсулирует небезопасный объект типа `union` в «безопасной коробке», -которая отслеживает последний присвоенный тип. Сразу после ини -циализации поле `Tag` содержит значение `Tag._tvoid`, по сути означающее, -что объект не инициализирован. При присваивании объединению неко -торого значения срабатывает оператор `opAssign`, устанавливающий тип -объекта в соответствии с типом присваиваемого значения. Чтобы полу -чить законченную реализацию, потребуется определить методы `opAssign(double)`, `opAssign(string)` и `opAssign(TaggedUnion[])` с соответствующи -ми функциями `getXxx()`. +Этот пример демонстрирует чисто классический способ использования `union` в качестве вспомогательного средства для определения так называемого *размеченного объединения* (*discriminated union*, *tagged union*), также известного как алгебраический тип. Размеченное объединение инкапсулирует небезопасный объект типа `union` в «безопасной коробке», которая отслеживает последний присвоенный тип. Сразу после инициализации поле `Tag` содержит значение `Tag._tvoid`, по сути означающее, что объект не инициализирован. При присваивании объединению некоторого значения срабатывает оператор `opAssign`, устанавливающий тип объекта в соответствии с типом присваиваемого значения. Чтобы получить законченную реализацию, потребуется определить методы `opAssign(double)`, `opAssign(string)` и `opAssign(TaggedUnion[])` с соответствующими функциями `getXxx()`. -Внутренний элемент типа `union` анонимен, то есть одновременно являет -ся и определением типа, и определением внутреннего элемента. Память -под анонимное объединение выделяется как под обычный внутренний -элемент структуры, и внутренние элементы этого объединения напря -мую видимы внутри структуры (как показывают методы `TaggedUnion`). -В общем случае можно определять как анонимные структуры, так и ано -нимные объединения, и вкладывать их как угодно. +Внутренний элемент типа `union` анонимен, то есть одновременно является и определением типа, и определением внутреннего элемента. Память под анонимное объединение выделяется как под обычный внутренний элемент структуры, и внутренние элементы этого объединения напрямую видимы внутри структуры (как показывают методы `TaggedUnion`). В общем случае можно определять как анонимные структуры, так и анонимные объединения, и вкладывать их как угодно. -В конце концов вы должны понять, что объединение не такое уж зло, -каким может показаться. Как правило, использовать объединение вме -сто того, чтобы играть типами с помощью выражения `cast`, – хороший -тон в общении между программистом и компилятором. Объединение -указателя и целого числа указывает сборщику мусора, что ему следует -быть осторожнее и не собирать этот указатель. Если вы сохраните ука -затель в целом числе и будете время от времени преобразовывать его на -зад к типу указателя (с помощью `cast`), результаты окажутся непред -сказуемыми, ведь сборщик мусора может забрать память, ассоцииро -ванную с этим тайным указателем. +В конце концов вы должны понять, что объединение не такое уж зло, каким может показаться. Как правило, использовать объединение вместо того, чтобы играть типами с помощью выражения `cast`, – хороший тон в общении между программистом и компилятором. Объединение указателя и целого числа указывает сборщику мусора, что ему следует быть осторожнее и не собирать этот указатель. Если вы сохраните указатель в целом числе и будете время от времени преобразовывать его назад к типу указателя (с помощью `cast`), результаты окажутся непредсказуемыми, ведь сборщик мусора может забрать память, ассоциированную с этим тайным указателем. ## 7.3. Перечисляемые значения -Типы, принимающие всего несколько определенных значений, оказа -лись очень полезными – настолько полезными, что язык Java после не -скольких лет героических попыток эмулировать перечисляемые типы -с помощью идиомы в конце концов добавил их к основным типам [8]. -Определить хорошие перечисляемые типы непросто – в C (и особенно -в C++) типу `enum` присущи свои странности. D попытался учесть пред -шествующий опыт, определив простое и полезное средство для работы -с перечисляемыми типами. +Типы, принимающие всего несколько определенных значений, оказались очень полезными – настолько полезными, что язык Java после нескольких лет героических попыток эмулировать перечисляемые типы с помощью идиомы в конце концов добавил их к основным типам. Определить хорошие перечисляемые типы непросто – в C (и особенно в C++) типу `enum` присущи свои странности. D попытался учесть предшествующий опыт, определив простое и полезное средство для работы с перечисляемыми типами. -Начнем с азов. Простейший способ применить `enum` – как сказать «да -вайте перечислим несколько символьных значений», не ассоциируя их -с новым типом: +Начнем с азов. Простейший способ применить `enum` – как сказать «давайте перечислим несколько символьных значений», не ассоциируя их с новым типом: ```d enum @@ -1920,21 +1400,14 @@ enum greet = "Hello"; ``` -С `enum` механизм автоматического определения типа работает так же, -как и с `auto`, поэтому в нашем примере переменные `pi` и `euler` имеют тип -`double`, a переменная `greet` – тип `string`. Чтобы определить одно или не -сколько перечисляемых значений определенного типа, укажите их спра -ва от ключевого слова `enum`: +С `enum` механизм автоматического определения типа работает так же, как и с `auto`, поэтому в нашем примере переменные `pi` и `euler` имеют тип `double`, a переменная `greet` – тип `string`. Чтобы определить одно или несколько перечисляемых значений определенного типа, укажите их справа от ключевого слова `enum`: ```d enum float verySmall = 0.0001, veryBig = 10000; enum dstring wideMsg = "Wide load"; ``` -Перечисляемые значения – это константы; они практически эквива -лентны литералам, которые обозначают. В частности, поддерживают те -же операции – например, невозможно получить адрес `pi`, как невоз -можно получить адрес `3.14`: +Перечисляемые значения – это константы; они практически эквивалентны литералам, которые обозначают. В частности, поддерживают те же операции – например, невозможно получить адрес `pi`, как невозможно получить адрес `3.14`: ```d auto x = pi; // Все в порядке, x обладает типом double @@ -1946,28 +1419,14 @@ void fun(ref double x) { fun(pi); // Ошибка! Невозможно получить адрес 3.14! ``` -Как показано выше, типы перечисляемых значений не ограничиваются -типом `int` – типы `double` и `string` также допустимы. Какие вообще типы -можно использовать с `enum`? Ответ прост: c `enum` можно использовать лю -бой основной тип и любую структуру. Есть лишь два требования к ини -циализирующему значению при определении перечисляемых значений: +Как показано выше, типы перечисляемых значений не ограничиваются типом `int` – типы `double` и `string` также допустимы. Какие вообще типы можно использовать с `enum`? Ответ прост: c `enum` можно использовать любой основной тип и любую структуру. Есть лишь два требования к инициализирующему значению при определении перечисляемых значений: -- инициализирующее значение должно быть вычислимым во время -компиляции; -- тип инициализирующего значения должен позволять копирование, -то есть в его определении не должно быть `@disable this(this)` (см. раз- -дел 7.1.3.4). +- инициализирующее значение должно быть вычислимым во времякомпиляции; +- тип инициализирующего значения должен позволять копирование, то есть в его определении не должно быть `@disable this(this)` (см. раздел 7.1.3.4). -Первое требование гарантирует независимость перечисляемого значе -ния от параметров времени исполнения. Второе требование обеспечива -ет возможность копировать значение; копия создается при каждом об -ращении к перечисляемому значению. +Первое требование гарантирует независимость перечисляемого значения от параметров времени исполнения. Второе требование обеспечивает возможность копировать значение; копия создается при каждом обращении к перечисляемому значению. -Невозможно определить перечисляемое значение типа `class`, поскольку -объекты классов должны всегда создаваться с помощью оператора `new` -(за исключением не представляющего интерес значения `null`), а выра -жение с `new` во время компиляции вычислить невозможно. Не будет не -ожиданностью, если в будущем это ограничение снимут или ослабят. +Невозможно определить перечисляемое значение типа `class`, поскольку объекты классов должны всегда создаваться с помощью оператора `new` (за исключением не представляющего интерес значения `null`), а выражение с `new` во время компиляции вычислить невозможно. Не будет неожиданностью, если в будущем это ограничение снимут или ослабят. Создадим перечисление значений типа `struct`: @@ -1983,23 +1442,17 @@ enum blue = Color(0, 0, 255); ``` -Когда бы вы ни использовали, например, идентификатор `green`, код бу -дет вести себя так, будто вместо этого идентификатора вы написали -`Color(0, 255, 0)`. +Когда бы вы ни использовали, например, идентификатор `green`, код будет вести себя так, будто вместо этого идентификатора вы написали `Color(0, 255, 0)`. ### 7.3.1. Перечисляемые типы -Можно дать имя группе перечисляемых значений, создав таким обра -зом новый тип на ее основе: +Можно дать имя группе перечисляемых значений, создав таким образом новый тип на ее основе: ```d enum OddWord { acini, alembicated, prolegomena, aprosexia } ``` -Члены именованной группы перечисляемых значений не могут иметь -разные типы; все перечисляемые значения должны иметь один и тот же -тип, поскольку пользователи могут впоследствии определять и исполь -зовать значения этого типа. Например: +Члены именованной группы перечисляемых значений не могут иметь разные типы; все перечисляемые значения должны иметь один и тот же тип, поскольку пользователи могут впоследствии определять и использовать значения этого типа. Например: ```d OddWord w; @@ -2009,27 +1462,15 @@ int x = w; // OddWord конвертируем в int, но н assert(x == 3); // Значения нумеруются по порядку: 0, 1, 2, ... ``` -Тип, автоматически определяемый для поименованного перечисления, – -`int`. Присвоить другой тип можно так: +Тип, автоматически определяемый для поименованного перечисления, – `int`. Присвоить другой тип можно так: ```d enum OddWord : byte { acini, alembicated, prolegomena, aprosexia } ``` -С новым определением (`byte` называют *базовым типом* `OddWord`) значе -ния идентификаторов перечисления не меняются, изменяется лишь -способ их хранения. Вы можете с таким же успехом назначить членам -перечисления тип `double` или `real`, но связанные с идентификаторами -значения останутся прежними: `0`, `1` и т. д. Но если сделать базовым ти -пом `OddWord` нечисловой тип, например `string`, то придется указать ини -циализирующее значение для каждого из значений, поскольку компи -лятору неизвестна никакая естественная последовательность, которой -он мог бы придерживаться. +С новым определением (`byte` называют *базовым типом* `OddWord`) значения идентификаторов перечисления не меняются, изменяется лишь способ их хранения. Вы можете с таким же успехом назначить членам перечисления тип `double` или `real`, но связанные с идентификаторами значения останутся прежними: `0`, `1` и т. д. Но если сделать базовым типом `OddWord` нечисловой тип, например `string`, то придется указать инициализирующее значение для каждого из значений, поскольку компилятору неизвестна никакая естественная последовательность, которой он мог бы придерживаться. -Возвратимся к числовым перечислениям. Присвоив какому-либо члену -перечисления особое значение, вы таким образом сбросите счетчик, ис -пользуемый компилятором для присваивания значений идентифика -торам. Например: +Возвратимся к числовым перечислениям. Присвоив какому-либо члену перечисления особое значение, вы таким образом сбросите счетчик, используемый компилятором для присваивания значений идентификаторам. Например: ```d enum E { a, b = 2, c, d = -1, e, f } @@ -2037,47 +1478,22 @@ assert(E.c == 3); assert(E.e == 0); ``` -Если два идентификатора перечисления получают одно и то же значе -ние (как в случае с `E.a` и `E.e`), конфликта нет. Фактически равные значе -ния можно создавать, даже не подозревая об этом – из-за непреодолимо -го желания типов с плавающей запятой удивить небдительных пользо -вателей: +Если два идентификатора перечисления получают одно и то же значение (как в случае с `E.a` и `E.e`), конфликта нет. Фактически равные значения можно создавать, даже не подозревая об этом – из-за непреодолимого желания типов с плавающей запятой удивить небдительных пользователей: ```d enum F : float { a = 1E30, b, c, d } assert(F.a == F.d); // Тест пройден ``` -Корень этой проблемы в том, что наибольшее значение типа `int`, кото -рое может быть представлено значением типа `float`, равно `16_777_216`, -и выход за эту границу сопровождается все возрастающими диапазона -ми целых значений, представляемых одним и тем же числом типа `float`. +Корень этой проблемы в том, что наибольшее значение типа `int`, которое может быть представлено значением типа `float`, равно `16_777_216`, и выход за эту границу сопровождается все возрастающими диапазонами целых значений, представляемых одним и тем же числом типа `float`. ### 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` является тип, поддерживающий сравнение во время компиляции с помощью оператора `<`. -Вы вправе определить внутри `enum` собственные значения `min`, `max` и `init`, -но поступать так не рекомендуется: обобщенный код частенько рассчи -тывает на то, что эти значения обладают особой семантикой. +Вы вправе определить внутри `enum` собственные значения `min`, `max` и `init`, но поступать так не рекомендуется: обобщенный код частенько рассчитывает на то, что эти значения обладают особой семантикой. -Один из часто задаваемых вопросов: «Можно ли добраться до имени пе -речисляемого значения?» Вне всяких сомнений, сделать это возможно -и на самом деле легко, но не с помощью встроенного механизма, а на ос -нове рефлексии времени компиляции. Рефлексия работает так: с неко -торым перечисляемым типом `Enum` связывается известная во время ком -пиляции константа `__traits(allMembers, Enum)`, которая содержит все чле -ны `Enum` в виде кортежа значений типа `string`. Поскольку строками мож -но манипулировать во время компиляции, как и во время исполнения, -такой подход дает значительную гибкость. Например, немного забежав -вперед, напишем функцию `toString`, которая возвращает строку, соот -ветствующую заданному перечисляемому значению. Функция парамет -ризирована перечисляемым типом. +Один из часто задаваемых вопросов: «Можно ли добраться до имени перечисляемого значения?» Вне всяких сомнений, сделать это возможно и на самом деле легко, но не с помощью встроенного механизма, а на основе рефлексии времени компиляции. Рефлексия работает так: с некоторым перечисляемым типом `Enum` связывается известная во время компиляции константа `__traits(allMembers, Enum)`, которая содержит все члены `Enum` в виде кортежа значений типа `string`. Поскольку строками можно манипулировать во время компиляции, как и во время исполнения, такой подход дает значительную гибкость. Например, немного забежав вперед, напишем функцию `toString`, которая возвращает строку, соответствующую заданному перечисляемому значению. Функция параметризирована перечисляемым типом. ```d string toString(E)(E value) if (is(E == enum)) @@ -2098,65 +1514,29 @@ void main() } ``` -Незнакомое пока выражение `mixin("E." ~ s)` – это *выражение* `mixin`. Вы -ражение `mixin` принимает строку, известную во время компиляции, -и просто вычисляет ее как обычное выражение в рамках текущего кон -текста. В нашем примере это выражение включает имя перечисления `E`, -оператор . для выбора внутренних элементов и переменную `s` для пере -бора идентификаторов перечисляемых значений. В данном случае s по -следовательно принимает значения `"acini"`, `"alembicated"`, ..., `"aprosexia"`. -Таким образом, конкатенированная строка примет вид `"E.acini"` и т. д., -а выражение `mixin` вычислит ее, сопоставив указанным идентификато -рам реальные значения. Обнаружив, что переданное значение равно оче -редному значению, вычисленному выражением `mixin`, функция `toString` -возвращает результат. Получив некорректный аргумент value, функ -ция `toString` могла бы порождать исключение, но чтобы упростить себе -жизнь, мы решили просто возвращать константу `null`. +Незнакомое пока выражение `mixin("E." ~ s)` – это *выражение* `mixin`. Выражение `mixin` принимает строку, известную во время компиляции, и просто вычисляет ее как обычное выражение в рамках текущего контекста. В нашем примере это выражение включает имя перечисления `E`, оператор `.` для выбора внутренних элементов и переменную `s` для перебора идентификаторов перечисляемых значений. В данном случае s последовательно принимает значения `"acini"`, `"alembicated"`, ..., `"aprosexia"`. Таким образом, конкатенированная строка примет вид `"E.acini"` и т. д., а выражение `mixin` вычислит ее, сопоставив указанным идентификаторам реальные значения. Обнаружив, что переданное значение равно очередному значению, вычисленному выражением `mixin`, функция `toString` возвращает результат. Получив некорректный аргумент value, функция `toString` могла бы порождать исключение, но чтобы упростить себе жизнь, мы решили просто возвращать константу `null`. -Рассмотренная функция `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.4. alias -В ряде случаев мы уже имели дело с `size_t` – целым типом без знака, -достаточно вместительным, чтобы представить размер любого объекта. -Тип `size_t` не определен языком, он просто принимает форму `uint` или -`ulong` в зависимости от адресного пространства конечной системы (32 -или 64 бита соответственно). +В ряде случаев мы уже имели дело с `size_t` – целым типом без знака, достаточно вместительным, чтобы представить размер любого объекта. Тип `size_t` не определен языком, он просто принимает форму `uint` или `ulong` в зависимости от адресного пространства конечной системы (32 или 64 бита соответственно). -Если бы вы открыли файл object.di, один из копируемых на компьютер -пользователя (а значит, и на ваш) при инсталляции компилятора D, то -нашли бы объявление примерно следующего вида: +Если бы вы открыли файл object.di, один из копируемых на компьютер пользователя (а значит, и на ваш) при инсталляции компилятора D, то нашли бы объявление примерно следующего вида: ```d alias typeof(int.sizeof) size_t; ``` -Свойство `.sizeof` точно измеряет размер типа в байтах; в данном случае -это тип `int`. Вместо `int` в примере мог быть любой другой тип; в данном -случае имеет значение не указанный тип, а тип размера, возвращаемый -оператором `typeof`. Компилятор измеряет размеры объектов, используя -`uint` на 32-разрядных архитектурах и `ulong` на 64-разрядных. Следова -тельно, конструкция `alias` позволяет назначить `size_t` синонимом `uint` -или `ulong`. +Свойство `.sizeof` точно измеряет размер типа в байтах; в данном случае это тип `int`. Вместо `int` в примере мог быть любой другой тип; в данном случае имеет значение не указанный тип, а тип размера, возвращаемый оператором `typeof`. Компилятор измеряет размеры объектов, используя `uint` на 32-разрядных архитектурах и `ulong` на 64-разрядных. Следовательно, конструкция `alias` позволяет назначить `size_t` синонимом `uint` или `ulong`. -Обобщенный синтаксис объявления с ключевым словом `alias` ничуть не -сложнее приведенного выше: +Обобщенный синтаксис объявления с ключевым словом `alias` ничуть не сложнее приведенного выше: ```d alias ‹существующийИдентификатор› ‹новыйИдентификатор›; ``` -В качестве идентификатора `‹существующийИдентификатор›` можно подста -вить все, у чего есть имя. Это может быть тип, переменная, модуль – ес -ли что-то обладает идентификатором, то для этого объекта можно соз -дать псевдоним. Например: +В качестве идентификатора `‹существующийИдентификатор›` можно подставить все, у чего есть имя. Это может быть тип, переменная, модуль – если что-то обладает идентификатором, то для этого объекта можно создать псевдоним. Например: ```d import std.stdio; @@ -2182,36 +1562,20 @@ unittest } ``` -Правила применения псевдонима просты: используйте псевдоним вез -де, где допустимо использовать исходный идентификатор. Именно это -делает компилятор, но с точностью до наоборот: он с пониманием заме -няет идентификатор-псевдоним оригинальным идентификатором. Да -же сообщения об ошибках и отлаживаемая программа могут «видеть -сквозь» псевдонимы и показывать исходные идентификаторы, что мо -жет оказаться неожиданным. Например, в некоторых сообщениях об -ошибках или в отладочных символах можно увидеть `immutable(char)[]` -вместо `string`. Но что именно будет показано, зависит от реализации -компилятора. +Правила применения псевдонима просты: используйте псевдоним везде, где допустимо использовать исходный идентификатор. Именно это делает компилятор, но с точностью до наоборот: он с пониманием заменяет идентификатор-псевдоним оригинальным идентификатором. Даже сообщения об ошибках и отлаживаемая программа могут «видеть сквозь» псевдонимы и показывать исходные идентификаторы, что может оказаться неожиданным. Например, в некоторых сообщениях об ошибках или в отладочных символах можно увидеть `immutable(char)[]` вместо `string`. Но что именно будет показано, зависит от реализации компилятора. -С помощью конструкции `alias` можно создавать псевдонимы псевдони -мов для идентификаторов, уже имеющих псевдонимы. Например: +С помощью конструкции `alias` можно создавать псевдонимы псевдонимов для идентификаторов, уже имеющих псевдонимы. Например: ```d alias int Int; alias Int MyInt; ``` -Здесь нет ничего особенного, просто следование обычным правилам: -к моменту определения псевдонима `MyInt` псевдоним `Int` уже будет заме -нен исходным идентификатором `int`, для которого `Int` является псевдо -нимом. +Здесь нет ничего особенного, просто следование обычным правилам: к моменту определения псевдонима `MyInt` псевдоним `Int` уже будет заменен исходным идентификатором `int`, для которого `Int` является псевдонимом. -Конструкцию `alias` часто применяют, когда требуется дать сложной це -почке идентификаторов более короткое имя или в связке с перегружен -ными функциями из разных модулей (см. раздел 5.5.2). +Конструкцию `alias` часто применяют, когда требуется дать сложной цепочке идентификаторов более короткое имя или в связке с перегруженными функциями из разных модулей (см. раздел 5.5.2). -Также конструкцию `alias` часто используют с параметризированными -структурами и классами. Например: +Также конструкцию `alias` часто используют с параметризированными структурами и классами. Например: ```d // Определить класс-контейнер @@ -2228,13 +1592,9 @@ unittest } ``` -Здесь общедоступный псевдоним `ElementType`, созданный классом `Container`, – единственный разумный способ обратиться из внешнего мира -к аргументу, привязанному к параметру `T` класса `Container`. Идентифи -катор `T` видим лишь внутри определения класса `Container`, но не снару -жи: выражение `Container!int.T` не компилируется. +Здесь общедоступный псевдоним `ElementType`, созданный классом `Container`, – единственный разумный способ обратиться из внешнего мира к аргументу, привязанному к параметру `T` класса `Container`. Идентификатор `T` видим лишь внутри определения класса `Container`, но не снаружи: выражение `Container!int.T` не компилируется. -Наконец, конструкция `alias` весьма полезна в сочетании с конструкци -ей `static if`. Например: +Наконец, конструкция `alias` весьма полезна в сочетании с конструкцией `static if`. Например: ```d // Из файла object.di @@ -2250,30 +1610,13 @@ else // Использовать ptrdiff_t ... ``` -С помощью объявления псевдоним `ptrdiff_t` привязывается к разным ти -пам в зависимости от того, по какой ветке статического условия пойдет -поток управления. Без этой возможности привязки код, которому потре -бовался такой тип, пришлось бы разместить в одной из веток `static if`. +С помощью объявления псевдоним `ptrdiff_t` привязывается к разным типам в зависимости от того, по какой ветке статического условия пойдет поток управления. Без этой возможности привязки код, которому потребовался такой тип, пришлось бы разместить в одной из веток `static if`. ## 7.5. Параметризированные контексты (конструкция template) -Мы уже рассмотрели средства, облегчающие параметризацию во время -компиляции (эти средства сродни шаблонам из C++ и родовым типам из -языков Java и C#), – это функции (см. раздел 5.3), параметризирован -ные классы (см. раздел 6.14) и параметризированные структуры, кото -рые подчиняются тем же правилам, что и параметризированные клас -сы. Тем не менее иногда во время компиляции требуется каким-либо -образом манипулировать типами, не определяя функцию, структуру -или класс. Один из механизмов, подходящих под это описание (широко -используемый в C++), – выбор того или иного типа в зависимости от -статически известного логического условия. При этом не определяется -никакой новый тип и не вызывается никакая функция – лишь создает -ся псевдоним для одного из существующих типов. +Мы уже рассмотрели средства, облегчающие параметризацию во время компиляции (эти средства сродни шаблонам из C++ и родовым типам из языков Java и C#), – это функции (см. раздел 5.3), параметризированные классы (см. раздел 6.14) и параметризированные структуры, которые подчиняются тем же правилам, что и параметризированные классы. Тем не менее иногда во время компиляции требуется каким-либо образом манипулировать типами, не определяя функцию, структуру или класс. Один из механизмов, подходящих под это описание (широко используемый в C++), – выбор того или иного типа в зависимости от статически известного логического условия. При этом не определяется никакой новый тип и не вызывается никакая функция – лишь создается псевдоним для одного из существующих типов. -Для случаев, когда требуется организовать параметризацию во время -компиляции без определения нового типа или функции, D предоставля -ет параметризированные контексты. Такой параметризированный кон -текст вводится следующим образом: +Для случаев, когда требуется организовать параметризацию во время компиляции без определения нового типа или функции, D предоставляет параметризированные контексты. Такой параметризированный контекст вводится следующим образом: ```d template Select(bool cond, T1, T2) @@ -2282,22 +1625,9 @@ template Select(bool cond, T1, T2) } ``` -Этот код – на самом деле лишь каркас для только что упомянутого меха -низма выбора во время компиляции. Скоро мы доберемся и до реализа -ции, а пока сосредоточимся на порядке объявления. Объявление с клю -чевым словом `template` вводит именованный контекст (в данном случае -это `Select`) с параметрами, вычисляемыми во время компиляции (в дан -ном случае это логическое значение и два типа). Объявить контекст -можно на уровне модуля, внутри определения класса, внутри определе -ния структуры, внутри любого другого объявления контекста, но не -внутри определения функции. +Этот код – на самом деле лишь каркас для только что упомянутого механизма выбора во время компиляции. Скоро мы доберемся и до реализации, а пока сосредоточимся на порядке объявления. Объявление с ключевым словом `template` вводит именованный контекст (в данном случае это `Select`) с параметрами, вычисляемыми во время компиляции (в данном случае это логическое значение и два типа). Объявить контекст можно на уровне модуля, внутри определения класса, внутри определения структуры, внутри любого другого объявления контекста, но не внутри определения функции. -В теле параметризированного контекста разрешается использовать все -те же объявления, что и обычно, кроме того, могут быть использованы -параметры контекста. Доступ к любому объявлению контекста можно -получить извне, расположив перед его именем имя контекста и ., на -пример: `Select!(true, int, double).foo`. Давайте прямо сейчас закончим -определение контекста `Select`, чтобы можно было поиграть с ним: +В теле параметризированного контекста разрешается использовать все те же объявления, что и обычно, кроме того, могут быть использованы параметры контекста. Доступ к любому объявлению контекста можно получить извне, расположив перед его именем имя контекста и `.`, например: `Select!(true, int, double).foo`. Давайте прямо сейчас закончим определение контекста `Select`, чтобы можно было поиграть с ним: ```d template Select(bool cond, T1, T2) @@ -2319,10 +1649,7 @@ unittest } ``` -Заметим, что тот же результат мы могли бы получить на основе струк -туры или класса, поскольку эти типы могут определять в качестве сво -их внутренних элементов псевдонимы, доступные с помощью обычного -синтаксиса с оператором `.` (точка): +Заметим, что тот же результат мы могли бы получить на основе структуры или класса, поскольку эти типы могут определять в качестве своих внутренних элементов псевдонимы, доступные с помощью обычного синтаксиса с оператором `.` (точка): ```d struct /* или class */ Select2(bool cond, T1, T2) @@ -2344,19 +1671,9 @@ unittest } ``` -Согласитесь, такое решение выглядит не очень привлекательно. К при -меру, для `Select2` в документации пришлось бы написать: «Не создавай -те объекты типа `Select2`! Он определен только ради псевдонима внутри -него!» Доступный специализированный механизм определения пара -метризированных контекстов позволяет избежать двусмысленности на -мерений, не вызывает недоумения и исключает возможность некоррект -ного использования. +Согласитесь, такое решение выглядит не очень привлекательно. К примеру, для `Select2` в документации пришлось бы написать: «Не создавайте объекты типа `Select2`! Он определен только ради псевдонима внутри него!» Доступный специализированный механизм определения параметризированных контекстов позволяет избежать двусмысленности намерений, не вызывает недоумения и исключает возможность некорректного использования. -В контексте, определенном с ключевым словом `template`, можно объяв -лять не только псевдонимы – там могут присутствовать самые разные -объявления. Определим еще один полезный шаблон. На этот раз это бу -дет шаблон, возвращающий логическое значение, которое сообщает, яв -ляется ли заданный тип строкой (в любой кодировке): +В контексте, определенном с ключевым словом `template`, можно объявлять не только псевдонимы – там могут присутствовать самые разные объявления. Определим еще один полезный шаблон. На этот раз это будет шаблон, возвращающий логическое значение, которое сообщает, является ли заданный тип строкой (в любой кодировке): ```d template isSomeString(T) @@ -2379,8 +1696,7 @@ unittest } ``` -Параметризированные контексты могут быть рекурсивными; к приме -ру, вот одно из возможных решений задачи с факториалом: +Параметризированные контексты могут быть рекурсивными; к примеру, вот одно из возможных решений задачи с факториалом: ```d template factorial(uint n) @@ -2392,35 +1708,15 @@ template factorial(uint n) } ``` -Несмотря на то что `factorial` является совершенным функциональным -определением, в данном случае это не лучший подход. При необходимо -сти вычислять значения во время компиляции, пожалуй, стоило бы -воспользоваться механизмом вычислений во время компиляции (см. -раздел 5.12). В отличие от приведенного выше шаблона `factorial`, функ -ция `factorial` более гибка, поскольку может вычисляться как во время -компиляции, так и во время исполнения. Конструкция `template` больше -всего подходит для манипуляции типами, имеющей место в `Select` -и `isSomeString`. +Несмотря на то что `factorial` является совершенным функциональным определением, в данном случае это не лучший подход. При необходимости вычислять значения во время компиляции, пожалуй, стоило бы воспользоваться механизмом вычислений во время компиляции (см. раздел 5.12). В отличие от приведенного выше шаблона `factorial`, функция `factorial` более гибка, поскольку может вычисляться как во время компиляции, так и во время исполнения. Конструкция `template` больше всего подходит для манипуляции типами, имеющей место в `Select` и `isSomeString`. ### 7.5.1. Одноименные шаблоны -Конструкция `template` может определять любое количество идентифи -каторов, но, как видно из предыдущих примеров, нередко в ней опреде -лен ровно один идентификатор. Обычно шаблон определяется лишь -с целью решить единственную задачу и в качестве результата сделать -доступным единственный идентификатор (такой как `Type` в случае `Select` -или `value` в случае `isSomeString`). +Конструкция `template` может определять любое количество идентификаторов, но, как видно из предыдущих примеров, нередко в ней определен ровно один идентификатор. Обычно шаблон определяется лишь с целью решить единственную задачу и в качестве результата сделать доступным единственный идентификатор (такой как `Type` в случае `Select` или `value` в случае `isSomeString`). -Необходимость помнить о том, что в конце вызова надо указать этот -идентификатор, и всегда его указывать может раздражать. Многие про -сто забывают добавить в конец `.Type`, а потом удивляются, почему вызов -`Select!(cond, A, B)` порождает таинственное сообщение об ошибке. +Необходимость помнить о том, что в конце вызова надо указать этот идентификатор, и всегда его указывать может раздражать. Многие просто забывают добавить в конец `.Type`, а потом удивляются, почему вызов `Select!(cond, A, B)` порождает таинственное сообщение об ошибке. -D помогает здесь, определяя правило, известное как фокус с одноимен -ным шаблоном: если внутри конструкции `template` определен иденти -фикатор, совпадающий с именем самого шаблона, то при любом после -дующем использовании имени этого шаблона в его конец будет автома -тически дописываться одноименный идентификатор. Например: +D помогает здесь, определяя правило, известное как фокус с одноименным шаблоном: если внутри конструкции `template` определен идентификатор, совпадающий с именем самого шаблона, то при любом последующем использовании имени этого шаблона в его конец будет автоматически дописываться одноименный идентификатор. Например: ```d template isNumeric(T) @@ -2435,16 +1731,9 @@ unittest } ``` -Если теперь некоторый код использует выражение `isNumeric!(T)`, компи -лятор в каждом случае автоматически заменит его на `isNumeric!(T).isNumeric`, чем освободит пользователя от хлопот с добавлением идентифи -катора в конец имени шаблона. +Если теперь некоторый код использует выражение `isNumeric!(T)`, компилятор в каждом случае автоматически заменит его на `isNumeric!(T).isNumeric`, чем освободит пользователя от хлопот с добавлением идентификатора в конец имени шаблона. -Шаблон, проделывающий фокус с «тезками», может определять внутри -себя и другие идентификаторы, но они будут попросту недоступны за -пределами этого шаблона. Дело в том, что компилятор заменяет иден -тификаторы на раннем этапе процесса поиска имен. Единственный спо -соб получить доступ к таким идентификаторам – обратиться к ним из -тела самого шаблона. Например: +Шаблон, проделывающий фокус с «тезками», может определять внутри себя и другие идентификаторы, но они будут попросту недоступны за пределами этого шаблона. Дело в том, что компилятор заменяет идентификаторы на раннем этапе процесса поиска имен. Единственный способ получить доступ к таким идентификаторам – обратиться к ним из тела самого шаблона. Например: ```d template isNumeric(T) @@ -2460,20 +1749,11 @@ unittest } ``` -Это сообщение об ошибке вызвано соблюдением правила об одноимен -ности: перед тем как делать что-либо еще, компилятор расширяет вызов -`isNumeric!(int)` до `isNumeric!(int).isNumeric`. Затем пользовательский код -делает попытку заполучить значение `isNumeric!(int).isNumeric.test1`, что -равносильно попытке получить внутренний элемент `test1` из логическо -го значения, отсюда и сообщение об ошибке. Короче говоря, используй -те одноименные шаблоны тогда и только тогда, когда хотите открыть -доступ лишь к одному идентификатору. Этот случай скорее частый, чем -редкий, поэтому одноименные шаблоны очень популярны и удобны. +Это сообщение об ошибке вызвано соблюдением правила об одноименности: перед тем как делать что-либо еще, компилятор расширяет вызов `isNumeric!(int)` до `isNumeric!(int).isNumeric`. Затем пользовательский код делает попытку заполучить значение `isNumeric!(int).isNumeric.test1`, что равносильно попытке получить внутренний элемент `test1` из логического значения, отсюда и сообщение об ошибке. Короче говоря, используйте одноименные шаблоны тогда и только тогда, когда хотите открыть доступ лишь к одному идентификатору. Этот случай скорее частый, чем редкий, поэтому одноименные шаблоны очень популярны и удобны. ### 7.5.2. Параметр шаблона this[^4] -Познакомившись с классами и структурами, можно параметризовать -наш обобщенный метод типом неявного аргумента `this`. Например: +Познакомившись с классами и структурами, можно параметризовать наш обобщенный метод типом неявного аргумента `this`. Например: ```d class Parent @@ -2494,40 +1774,21 @@ unittest } ``` -Параметр шаблона `this T` предписывает компилятору в теле `getName` -считать `T` псевдонимом `typeof(this)`. +Параметр шаблона `this T` предписывает компилятору в теле `getName` считать `T` псевдонимом `typeof(this)`. -В обычный статический метод класса не передаются никакие скрытые -параметры, поэтому невозможно определить, для какого конкретно -класса вызван этот метод. В приведенном примере компилятор создает -три экземпляра шаблонного метода `Parent.getName(this T)()`: `Parent.getName()`, `Derived1.getName()` и `Derived2.getName()`. +В обычный статический метод класса не передаются никакие скрытые параметры, поэтому невозможно определить, для какого конкретно класса вызван этот метод. В приведенном примере компилятор создает три экземпляра шаблонного метода `Parent.getName(this T)()`: `Parent.getName()`, `Derived1.getName()` и `Derived2.getName()`. -Также параметр `this` удобен в случае, когда один метод нужно исполь -зовать для разных квалификаторов неизменяемости объекта (см. гла -ву 8). +Также параметр `this` удобен в случае, когда один метод нужно использовать для разных квалификаторов неизменяемости объекта (см. главу 8). ## 7.6. Инъекции кода с помощью конструкции mixin template -При некоторых программных решениях приходится добавлять шаблон -ный код (такой как определения данных и методов) в одну или несколь -ко реализаций классов. К типичным примерам относятся поддержка -сериализации, шаблон проектирования «Наблюдатель» и передача -событий в оконных системах. +При некоторых программных решениях приходится добавлять шаблонный код (такой как определения данных и методов) в одну или несколько реализаций классов. К типичным примерам относятся поддержка сериализации, шаблон проектирования «Наблюдатель» и передача событий в оконных системах. -Для этих целей можно было бы воспользоваться механизмом наследова -ния, но поскольку реализуется лишь одиночное наследование, опреде -лить для заданного класса несколько источников шаблонного кода не -возможно. Иногда необходим механизм, позволяющий просто вставить -в класс некоторый готовый код, вместо того чтобы писать его вручную. +Для этих целей можно было бы воспользоваться механизмом наследования, но поскольку реализуется лишь одиночное наследование, определить для заданного класса несколько источников шаблонного кода невозможно. Иногда необходим механизм, позволяющий просто вставить в класс некоторый готовый код, вместо того чтобы писать его вручную. -Здесь-то и пригодится конструкция `mixin template` (шаблон `mixin`). Стоит -отметить, что сейчас это средство в основном экспериментальное. Воз -можно, в будущих версиях языка шаблоны `mixin` заменит более общий -инструмент AST-макросов. +Здесь-то и пригодится конструкция `mixin template` (шаблон `mixin`). Стоит отметить, что сейчас это средство в основном экспериментальное. Возможно, в будущих версиях языка шаблоны `mixin` заменит более общий инструмент AST-макросов. -Шаблон `mixin` определяется почти так же, как параметризированный -контекст (шаблон), о котором недавно шла речь. Пример шаблона `mixin`, -определяющего переменную и функции для ее чтения и записи: +Шаблон `mixin` определяется почти так же, как параметризированный контекст (шаблон), о котором недавно шла речь. Пример шаблона `mixin`, определяющего переменную и функции для ее чтения и записи: ```d mixin template InjectX() @@ -2564,16 +1825,9 @@ void fun() } ``` -Теперь этот код определяет переменную и две обслуживающие ее функ -ции на уровне модуля, внутри класса `A` и внутри функции `fun` – как буд -то тело `InjectX` было вставлено вручную. В частности, потомки класса A -могут переопределять методы `getX` и `setX`, как если бы сам класс опреде -лял их. Копирование и вставка без неприятного дублирования кода – -вот что такое `mixin template`. +Теперь этот код определяет переменную и две обслуживающие ее функции на уровне модуля, внутри класса `A` и внутри функции `fun` – как будто тело `InjectX` было вставлено вручную. В частности, потомки класса `A` могут переопределять методы `getX` и `setX`, как если бы сам класс определял их. Копирование и вставка без неприятного дублирования кода – вот что такое `mixin template`. -Конечно же, следующий логический шаг – подумать о том, что `InjectX` -не принимает никаких параметров, но производит впечатление, что мог -бы, – и действительно может: +Конечно же, следующий логический шаг – подумать о том, что `InjectX` не принимает никаких параметров, но производит впечатление, что мог бы, – и действительно может: ```d mixin template InjectX(T) @@ -2595,20 +1849,14 @@ mixin InjectX!int; mixin InjectX!double; ``` -Но на самом деле такие вставки приводят к двусмысленности: что если -вы сделаете две рассмотренные подстановки, а затем пожелаете восполь -зоваться функцией `getX`? Есть две функции с этим именем, так что про -блема с двусмысленностью очевидна. Чтобы решить этот вопрос, D по -зволяет вводить *имена* для конкретных подстановок в шаблоны `mixin`: +Но на самом деле такие вставки приводят к двусмысленности: что если вы сделаете две рассмотренные подстановки, а затем пожелаете воспользоваться функцией `getX`? Есть две функции с этим именем, так что проблема с двусмысленностью очевидна. Чтобы решить этот вопрос, D позволяет вводить *имена* для конкретных подстановок в шаблоны `mixin`: ```d mixin InjectX!int MyInt; mixin InjectX!double MyDouble; ``` -Задав такие определения, вы можете недвусмысленно обратиться к внут -ренним элементам любого из шаблонов `mixin`, просто указав нужный -контекст: +Задав такие определения, вы можете недвусмысленно обратиться к внутренним элементам любого из шаблонов `mixin`, просто указав нужный контекст: ```d MyInt.setX(5); @@ -2617,28 +1865,17 @@ MyDouble.setX(5.5); assert(MyDouble.getX() == 5.5); ``` -Таким образом, шаблоны `mixin` – это *почти* как копирование и вставка; -вы можете многократно копировать и вставлять код, а потом указы -вать, к какой именно вставке хотите обратиться. +Таким образом, шаблоны `mixin` – это *почти* как копирование и вставка; вы можете многократно копировать и вставлять код, а потом указывать, к какой именно вставке хотите обратиться. ### 7.6.1. Поиск идентификаторов внутри mixin -Самая большая разница между шаблоном `mixin` и обычным шаблоном -(в том виде, как он определен в разделе 7.5), способная вызвать больше -всего вопросов, – это поиск имен. +Самая большая разница между шаблоном `mixin` и обычным шаблоном (в том виде, как он определен в разделе 7.5), способная вызвать больше всего вопросов, – это поиск имен. -Шаблоны исключительно модульны: код внутри шаблона ищет иденти -фикаторы в месте *определения* шаблона. Это положительное качество: -оно гарантирует, что, проанализировав определение шаблона, вы уже -ясно представляете его содержимое и осознаете, как он работает. +Шаблоны исключительно модульны: код внутри шаблона ищет идентификаторы в месте *определения* шаблона. Это положительное качество: оно гарантирует, что, проанализировав определение шаблона, вы уже ясно представляете его содержимое и осознаете, как он работает. -Шаблон `mixin`, напротив, ищет идентификаторы в месте *подстановки*, -а это означает, что понять поведение шаблона `mixin` можно только с уче -том контекста, в котором вы собираетесь этот шаблон использовать. +Шаблон `mixin`, напротив, ищет идентификаторы в месте *подстановки*, а это означает, что понять поведение шаблона `mixin` можно только с учетом контекста, в котором вы собираетесь этот шаблон использовать. -Чтобы проиллюстрировать разницу, рассмотрим следующий пример, -в котором идентификаторы объявляются как в месте определения, так -и в месте подстановки: +Чтобы проиллюстрировать разницу, рассмотрим следующий пример, в котором идентификаторы объявляются как в месте определения, так и в месте подстановки: ```d import std.stdio; @@ -2672,48 +1909,23 @@ void main() Найдено на уровне функции ``` -Склонность шаблонов `mixin` привязываться к локальным идентифика -торам придает им выразительности, но следовать их логике становится -сложно. Такое поведение делает шаблоны `mixin` применимыми лишь -в ограниченном количестве случаев; прежде чем доставать из ящика -с инструментами эти особенные ножницы, необходимо семь раз отме -рить. +Склонность шаблонов `mixin` привязываться к локальным идентификаторам придает им выразительности, но следовать их логике становится сложно. Такое поведение делает шаблоны `mixin` применимыми лишь в ограниченном количестве случаев; прежде чем доставать из ящика с инструментами эти особенные ножницы, необходимо семь раз отмерить. ## 7.7. Итоги -Классы позволяют эффективно представить далеко не любую абстрак -цию. Например, они не подходят для мелкокалиберных объектов, кон -текстно-зависимых ресурсов и типов значений. Этот пробел восполня -ют структуры. В частности, благодаря конструкторам и деструкторам -легко определять типы контекстно-зависимых ресурсов. +Классы позволяют эффективно представить далеко не любую абстракцию. Например, они не подходят для мелкокалиберных объектов, контекстно-зависимых ресурсов и типов значений. Этот пробел восполняют структуры. В частности, благодаря конструкторам и деструкторам легко определять типы контекстно-зависимых ресурсов. -Объединения – низкоуровневое средство, позволяющее хранить разные -типы данных в одной области памяти с перекрыванием. +Объединения – низкоуровневое средство, позволяющее хранить разные типы данных в одной области памяти с перекрыванием. -Перечисления – это обычные отдельные значения, определенные поль -зователем. Перечислению может быть назначен новый тип, что позво -ляет более точно проверять типы значений, определенных в рамках -этого типа. +Перечисления – это обычные отдельные значения, определенные пользователем. Перечислению может быть назначен новый тип, что позволяет более точно проверять типы значений, определенных в рамках этого типа. -`alias` – очень полезное средство, позволяющее привязать один иденти -фикатор к другому. Нередко псевдоним – единственное средство полу -чить извне доступ к идентификатору, вычисляемому в рамках вложен -ной сущности, или к длинному и сложному идентификатору. +`alias` – очень полезное средство, позволяющее привязать один идентификатор к другому. Нередко псевдоним – единственное средство получить извне доступ к идентификатору, вычисляемому в рамках вложенной сущности, или к длинному и сложному идентификатору. -Параметризированные контексты, использующие конструкцию `template`, весьма полезны для определения вычислений во время компиля -ции, таких как интроспекция типов и определение особенностей типов. -Одноименные шаблоны позволяют предоставлять абстракции в очень -удобной, инкапсулированной форме. +Параметризированные контексты, использующие конструкцию `template`, весьма полезны для определения вычислений во время компиляции, таких как интроспекция типов и определение особенностей типов. Одноименные шаблоны позволяют предоставлять абстракции в очень удобной, инкапсулированной форме. -Кроме того, предлагаются параметризированные контексты, прини -мающие форму шаблонов `mixin`, которые во многом ведут себя подобно -макросам. В будущем шаблоны `mixin` может заменить развитое средст -во AST-макросов. +Кроме того, предлагаются параметризированные контексты, принимающие форму шаблонов `mixin`, которые во многом ведут себя подобно макросам. В будущем шаблоны `mixin` может заменить развитое средство AST-макросов. [^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4). [^2]: Термин «клуктура» предложил Бартош Милевски. -[^3]: Кроме того, ‹код1› может сохранить указатель на значение w, которое исполь -зует ‹код2›. -[^4]: На момент написания оригинала книги данная возможность отсутствова -ла, но поскольку теперь она существует, мы добавили ее описание в пере -вод. – Прим. науч. ред. +[^3]: Кроме того, `‹код1›` может сохранить указатель на значение `w`, которое использует `‹код2›`. +[^4]: На момент написания оригинала книги данная возможность отсутствовала, но поскольку теперь она существует, мы добавили ее описание в перевод. – *Прим. науч. ред.*