Alexander Zhirov 2 years ago
parent
commit
0e79ca3ab4

+ 2464 - 0
07-другие-пользовательские-типы/README.md

@@ -81,4 +81,2468 @@ struct Widget
 
 [В начало ⮍](#7-1-структуры) [Наверх ⮍](#7-другие-пользовательские-типы)
 
+### 7.1.1. Семантика копирования
+
+Несколько заметных на глаз различий между структурами и классами
+есть следствие менее очевидных семантических различий. Повторим
+эксперимент, который мы уже проводили, обсуждая классы в разде-
+ле 6.2. На этот раз создадим структуру и объект с одинаковыми поля
+ми, а затем сравним поведение этих типов при копировании:
+
+```d
+class C
+{
+    int x = 42;
+    double y = 3.14;
+}
+
+struct S
+{
+    int x = 42;
+    double y = 3.14;
+}
+
+unittest
+{
+    C c1 = new C;
+    S s1;                // Никакого оператора new для S: память выделяется в стеке
+    auto c2 = c1;
+    auto s2 = s1;
+    c2.x = 100;
+    s2.x = 100;
+    assert(c1.x == 100); // c1 и c2 ссылаются на один и тот же объект...
+    assert(s1.x == 42);  // ...а s2 – это настоящая копия s1
+}
+```
+
+При работе со структурами нет никаких ссылок, которые можно привя
+зывать и перепривязывать с помощью операций инициализации и при
+сваивания. Каждое имя экземпляра структуры связано с отдельным
+значением. Как уже говорилось, объект-структура ведет себя *как значение*, а объект-класс – *как ссылка*. На рис. 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` бессмысленно и порождает ошибку
+во время компиляции.
+
+### 7.1.2. Передача объекта-структуры в функцию
+
+Поскольку объект типа `struct` ведет себя как значение, он и передается
+в функцию по значению.
+
+```d
+struct S
+{
+    int a, b, c;
+    double x, y, z;
+}
+
+void fun(S s)
+{
+    // fun получает копию
+    ...
+}
+```
+
+Передать объект-структуру по ссылке можно с помощью аргумента
+с ключевым словом `ref` (см. раздел 5.2.1):
+
+```d
+void fun(ref S s) // fun получает ссылку
+{
+    ...
+}
+```
+
+Раз уж мы заговорили о `ref`, отметим, что `this` передается по ссылке
+внутрь методов структуры `S` в виде скрытого параметра `ref S`.
+
+### 7.1.3. Жизненный цикл объекта-структуры
+
+В отличие от объектов-классов, объектам-структурам не свойственно
+бесконечное время жизни (lifetime). Время жизни для них четко огра
+ничено – так же как для временных (стековых) объектов функций.
+Чтобы создать объект-структуру, задайте имя нужного типа, как если
+бы вы вызывали функцию:
+
+```d
+import std.math;
+
+struct Test
+{
+    double a = 0.4;
+    double b;
+}
+
+unittest
+{
+    // Чтобы создать объект, используйте имя структуры так, как используете функцию
+    auto t = Test();
+    assert(t.a == 0.4 && IsNaN(t.b));
+}
+```
+
+Вызов `Test()` создает объект-структуру, все поля которого инициализи
+рованы по умолчанию. В нашем случае это означает, что поле `t.a` при
+нимает значение `0.4`, а `t.b` остается инициализированным значением
+`double.init`.
+
+Вызовы `Test(1)` и `Test(1.5, 2.5)` также разрешены и инициализируют по
+ля объекта в порядке их объявления. Продолжим предыдущий пример:
+
+```d
+unittest
+{
+    auto t1 = Test(1);
+    assert(t1.a == 1 && IsNaN(t1.b));
+    auto t2 = Test(1.5, 2.5);
+    assert(t2.a == 1.5 && t2.b == 2.5);
+}
+```
+
+Поначалу может раздражать разница в синтаксисе выражения, создаю
+щего объект-структуру `Test(‹аргументы›)`, и выражения, создающего объ
+ект-класс `new Test(‹аргументы›)`. D мог бы отказаться от использования
+ключевого слова new при создании объектов-классов, но это `new` напоми
+нает программисту, что выполняется операция выделения памяти (то
+есть необычное действие).
+
+#### 7.1.3.1. Конструкторы
+
+Конструктор структуры определяется так же, как конструктор класса
+(см. раздел 6.3.1):
+
+```d
+struct Test
+{
+    double a = 0.4;
+    double b;
+    this(double b)
+    {
+        this.b = b;
+    }
+}
+
+unittest
+{
+    auto t = Test(5);
+}
+```
+
+Присутствие хотя бы одного пользовательского конструктора блокирует
+все упомянутые выше конструкторы, инициализирующие поля струк
+туры:
+
+```d
+auto t1 = Test(1.1, 1.2); // Ошибка! Нет конструктора, соответствующего вызову Test(double, double)
+```
+
+Есть важное исключение: компилятор всегда определяет конструктор
+без аргументов:
+
+```d
+auto t2 = Test(); // Все в порядке, создается объект с "начинкой" по умолчанию
+```
+
+Кроме того, пользовательский код не может определить собственный
+конструктор без аргументов:
+
+```d
+struct Test
+{
+    double a = 0.4;
+    double b;
+    this() { b = 0; } // Ошибка! Структура не может определить конструктор по умолчанию!
+}
+```
+
+Зачем нужно такое ограничение? Все из-за `T.init` – значения по умолча
+нию, определяемого каждым типом. Оно должно быть статически из
+вестно, что противоречит существованию конструктора по умолчанию,
+выполняющего произвольный код. (Для классов `T.init` – это пустая
+ссылка `null`, а не объект, построенный по умолчанию.) Правило для всех
+структур: конструктор по умолчанию инициализирует все поля объек
+та-структуры значениями по умолчанию.
+
+#### 7.1.3.2. Делегирование конструкторов
+
+Скопируем пример из раздела 6.3.2 с заменой ключевого слова `class` на
+`struct`:
+
+```d
+struct Widget
+{
+    this(uint height)
+    {
+        this(1, height); // Положиться на другой конструктор
+    }
+    this(uint width, uint height)
+    {
+        this.width = width;
+        this.height = height;
+    }
+    uint width, height;
+    ...
+}
+```
+
+Код запускается, не требуя внесения каких-либо других изменений.
+Так же как и классы, структуры позволяют одному конструктору деле
+гировать построение объекта другому конструктору с теми же ограни
+чениями.
+
+#### 7.1.3.3. Алгоритм построения
+
+Классу приходится заботиться о выделении динамической памяти
+и инициализации своего базового подобъекта (см. раздел 6.3.3). Со
+структурами все гораздо проще, поскольку выделение памяти – явный
+шаг алгоритма построения. Алгоритм построения объекта-структуры
+типа `T` по шагам:
+
+1. Скопировать значение `T.init` в память, где будет размещен объект,
+путем копирования «сырой» памяти (а-ля `memcpy`).
+2. Вызвать конструктор, если нужно.
+
+Если инициализация некоторых или всех полей структуры выглядит
+как `= void`, объем работ на первом шаге можно сократить, хотя и редко
+намного, зато такой маневр часто порождает трудноуловимые ошибки
+в вашем коде (тем не менее случай оправданного применения сокра
+щенной инициализации иллюстрирует пример с классом `Transmogrifier`
+в разделе 6.3.3).
+
+#### 7.1.3.4. Конструктор копирования this(this)
+
+Предположим, требуется определить объект, который содержит ло
+кальный (`private`) массив и предоставляет ограниченный API для ма
+нипуляции этим массивом:
+
+```d
+struct Widget
+{
+    private int[] array;
+    this(uint length)
+    {
+        array = new int[length];
+    }
+    int get(size_t offset)
+    {
+        return array[offset];
+    }
+    void set(size_t offset, int value)
+    {
+        array[offset] = value;
+    }
+}
+```
+
+У класса `Widget`, определенного таким образом, есть проблема: при ко
+пировании объектов типа `Widget` между копиями создается отдаленная
+зависимость. Судите сами:
+
+```d
+unittest
+{
+    auto w1 = Widget(10);
+    auto w2 = w1;
+    w1.set(5, 100);
+    w2.set(5, 42);            // Также изменяет элемент w1.array[5]!
+    assert(w1.get(5) == 100); // Не проходит!?!
+}
+```
+
+В чем проблема? Копирование содержимого `w1` в `w2` «поверхностно», то
+есть оно выполняется поле за полем, без транзитивного копирования,
+на какую бы память косвенно ни ссылалось каждое из полей. При ко
+пировании массива память под новый массив не выделяется; копиру
+ются лишь границы массива (см. раздел 4.1.4). После копирования `w1`
+и `w2` действительно обладают различными полями с массивами, но ссы
+лаются эти поля на одну и ту же область памяти. Такой объект, являю
+щийся значением, но содержащий неявные разделяемые ссылки, мож
+но в шутку назвать «клуктурой», то есть гибридом структуры (семанти
+ка значения) и класса (семантика ссылки)[^2].
+
+Обычно требуется, чтобы структура действительно вела себя как значе
+ние, то есть чтобы копия становилась полностью независимой от своего
+источника. Для этого определите конструктор копирования так:
+
+```d
+struct Widget
+{
+    private int[] array;
+    this(uint length)
+    {
+        array = new int[length];
+    }
+    // Конструктор копирования
+    this(this)
+    {
+        array = array.dup;
+    }
+    // Как раньше
+    int get(size_t offset) { return array[offset]; }
+    void set(size_t offset, int value) { array[offset] = value; }
+}
+```
+
+Конструктор копирования вступает в силу во время копирования объ
+екта. Чтобы инициализировать объект `приемник` с помощью объекта `источник` того же типа, компилятор должен выполнить следующие шаги:
+
+1. Скопировать участок «сырой» памяти объекта `источник` в участок
+«сырой» памяти объекта `приемник`.
+2. Транзитивно для каждого поля, содержащего другие поля (то есть
+поля, содержащего другое поле, содержащее третье поле, ...), для ко
+торого определен метод `this(this)`, вызвать эти конструкторы снизу
+вверх (начиная от наиболее глубоко вложенного поля).
+3. Вызвать метод `this(this)` с объектом приемник.
+
+Оригинальное название конструктора копирования «postblit construc
+tor» происходит от «blit» – популярной аббревиатуры понятия «block
+transfer», означавшего копирование «сырой» памяти. Язык применяет
+«сырое» копирование при инициализации и разрешает сразу после это
+го воспользоваться ловушкой. В предыдущем примере конструктор ко
+пирования превращает только что полученный псевдоним массива в на
+стоящую, полномасштабную копию, гарантируя, что с этого момента
+между объектом-оригиналом и объектом-копией не будет ничего обще
+го. Теперь, после добавления конструктора копирования, модуль легко
+проходит этот тест:
+
+```d
+unittest
+{
+    auto w1 = Widget(10);
+    auto w2 = w1;             // this(this) здесь вызывается с w2
+    w1.set(5, 100);
+    w2.set(5, 42);
+    assert(w1.get(5) == 100); // Пройдено
+}
+```
+
+Вызов конструктора копирования вставляется в каждом случае копи
+рования какого-либо объекта при явном или неявном создании новой
+переменной. Например, при передаче объекта типа `Widget` по значению
+в функцию также создается копия:
+
+```d
+void fun(Widget w) // Передать по значению
+{
+    w.set(2, 42);
+}
+
+void gun(ref Widget w) // Передать по ссылке
+{
+    w.set(2, 42);
+}
+
+unittest
+{
+    auto w1 = Widget(10);
+    w1.set(2, 100);
+    fun(w1);                  // Здесь создается копия
+    assert(w1.get(2) == 100); // Тест пройден
+    gun(w1);                  // А здесь копирования нет
+    assert(w1.get(2) == 42);  // Тест пройден
+}
+```
+
+Второй шаг (часть с «транзитивным полем») процесса конструирования
+при копировании заслуживает особого внимания. Основанием для та
+кого поведения является *инкапсуляция*: конструктор копирования
+объекта-структуры должен быть вызван даже тогда, когда эта структу
+ра встроена в другую. Предположим, например, что мы решили сделать
+`Widget` членом другой структуры, которая в свою очередь является чле
+ном третьей структуры:
+
+```d
+struct Widget2
+{
+    Widget w1;
+    int x;
+}
+
+struct Widget3
+{
+    Widget2 w2;
+    string name;
+    this(this)
+    {
+        name = name ~ " (copy)";
+    }
+}
+```
+
+Теперь, если потребуется копировать объекты, содержащие другие объ
+екты типа `Widget`, будет очень некстати, если компилятор забудет, как
+нужно копировать подобъекты типа `Widget`. Вот почему при копирова
+нии объектов типа `Widget2` инициируется вызов конструктора `this(this)`
+для подобъекта `w1`, невзирая на то, что `Widget2` вообще об этом ничего не
+знает. Кроме того, при копировании объектов типа `Widget3` конструктор
+`this(this)` по-прежнему вызывается применительно к полю `w1` поля `w2`.
+Внесем ясность:
+
+```d
+unittest
+{
+    Widget2 a;
+    a.w1 = Widget(10);                      // Выделить память под данные
+    auto b = a;                             // this(this) вызывается для b.w1
+    assert(a.w1.array !is b.w1.array);      // Тест пройден
+    Widget3 c;
+    c.w2.w1 = Widget(20);
+    auto d = c;                              // this(this) вызывается для d.w2.w1
+    assert(c.w2.w1.array !is d.w2.w1.array); // Тест пройден
+}
+```
+
+Вкратце, если вы определите для некоторой структуры конструктор ко
+пирования `this(this)`, компилятор позаботится о том, чтобы конструк
+тор копирования вызывался в каждом случае копирования этого объ
+екта-структуры независимо от того, является ли он самостоятельным
+объектом или частью более крупного объекта-структуры.
+
+#### 7.1.3.5. Аргументы в пользу this(this)
+
+Зачем был введен конструктор копирования? Ведь ничего подобного
+в других языках пока нет. Почему бы просто не передавать исходный
+объект в будущую копию (как это делает C++)?
+
+```d
+// Это не D
+struct S
+{
+    this(S another) { ... }
+// Или
+    this(ref S another) { ... }
+}
+```
+
+Опыт с C++ показал, что основная причина неэффективности программ
+на C++ – злоупотребление копированием объектов. Чтобы сократить по
+тери эффективности по этой причине, C++ устанавливает ряд случаев,
+в которых компилятор может пропускать вызов конструктора копиро
+вания (copy elision). Правила для этих случаев очень быстро усложни
+лись, но все равно не охватывали все моменты, когда можно обойтись
+без конструирования, то есть проблема осталась не решенной. Развива
+ющийся стандарт C++ затрагивает эти вопросы, определяя новый тип
+«ссылка на r-значение», позволяющий пользователю управлять пропус
+ками вызова конструктора копирования, но плата за это – еще большее
+усложнение языка.
+
+Благодаря конструктору копирования подход D становится простым
+и во многом автоматизируемым. Начнем с того, что объекты в D долж
+ны быть *перемещаемыми*, то есть не должны зависеть от своего располо
+жения: копирование «сырой» памяти позволяет переместить объект
+в другую область памяти, не нарушая его целостность. Тем не менее это
+ограничение означает, что объект не может содержать так называемые
+*внутренние указатели* – адреса подобъектов, являющихся его частя
+ми. Без этой техники можно обойтись, так что D попросту ее исключает.
+Создавать объекты с внутренними указателями в D запрещается, и ком
+пилятор, как и подсистема времени исполнения, вправе предполагать,
+что это правило соблюдается. Перемещаемые объекты открывают для
+компилятора и подсистемы времени исполнения (например, для сбор
+щика мусора) большие возможности, позволяющие программам стать
+более быстрыми и компактными.
+
+Благодаря перемещаемости объектов копирование объектов становится
+логическим продолжением перемещения объектов: конструктор копи
+рования `this(this)` делает копирование объектов эквивалентом переме
+щения с возможной последующей пользовательской обработкой. Таким
+образом, пользовательский код не может изменить поля исходного объ
+екта (что очень хорошо, поскольку копирование не должно затрагивать
+объект-источник), но зато может корректировать поля, которые не долж
+ны неявно разделять состояние с объектом-источником. Чтобы избежать
+лишнего копирования, компилятор вправе по собственному усмотре
+нию не вставлять вызов `this(this)`, если может доказать, что источник
+копии не будет использован после завершения процесса копирования.
+Рассмотрим, например, функцию, возвращающую объект типа `Widget`
+(определенный выше) по значению:
+
+```d
+Widget hun(uint x)
+{
+    return Widget(x * 2);
+}
+
+unittest
+{
+    auto w = hun(1000);
+    ...
+}
+```
+
+Наивный подход: просто создать объект типа `Widget` внутри функции
+`hun`, а затем скопировать его в переменную `w`, применив побитовое копи
+рование с последующим вызовом `this(this)`. Но это было бы слишком
+расточительно: D полагается на перемещаемость объектов, так почему
+бы попросту не переместить в переменную `w` уже отживший свое времен
+ный объект, созданный функцией `hun`? Разницу никто не заметит, по
+скольку после того, как функция `hun` вернет результат, временный объ
+ект уже не нужен. Если в лесу упало дерево и никто этого не слышит, то
+легче переместить его, чем копировать. Похожий (но не идентичный)
+случай:
+
+```d
+Widget iun(uint x)
+{
+    auto result = Widget(x * 2);
+    ...
+    return result;
+}
+
+unittest
+{
+    auto w = iun(1000);
+    ...
+}
+```
+
+В этом случае переменная `result` тоже уходит в небытие сразу же после
+того, как `iun` вернет управление, поэтому в вызове `this(this)` необходи
+мости нет. Наконец, еще более тонкий случай:
+
+```d
+void jun(Widget w)
+{
+    ...
+}
+
+unittest
+{
+    auto w = Widget(1000);
+    ... // ‹код1›
+    jun(w);
+    ... // ‹код2›
+}
+```
+
+В этом случае сложнее выяснить, можно ли избавиться от вызова
+`this(this)`. Вполне вероятно, что `‹код2›` продолжает использовать `w`, и то
+гда перемещение этого значения из `unittest` в `jun` было бы некоррект
+ным[^3].
+
+Ввиду всех перечисленных соображений в D приняты следующие пра
+вила пропуска вызова конструктора копирования:
+
+- Все анонимные r-значения перемещаются, а не копируются. Вызов
+конструктора копирования `this(this)` всегда пропускается, если ори
+гиналом является анонимное r-значение (то есть временный объект,
+как в функции `hun` выше).
+- В случае именованных временных объектов, которые создаются
+внутри функции и располагаются в стеке, а затем возвращаются этой
+функцией в качестве результата, вызов конструктора копирования
+`this(this)` пропускается.
+- Нет никаких гарантий, что компилятор воспользуется другими воз
+можностями пропустить вызов конструктора копирования.
+
+Но иногда требуется предписать компилятору выполнить перемеще
+ние. Фактически это выполняет функция `move` из модуля `std.algorithm`
+стандартной библиотеки:
+
+```d
+import std.algorithm;
+
+void kun(Widget w)
+{
+    ...
+}
+
+unittest
+{
+    auto w = Widget(1000);
+    ... // ‹код1›
+    // Вставлен вызов move
+    kun(move(w));
+    assert(w == Widget.init); // Пройдено
+    ... // ‹код2›
+}
+```
+
+Вызов функции `move` гарантирует, что `w` будет перемещена, а ее содержи
+мое будет заменено пустым, сконструированным по умолчанию объек
+том типа `Widget`. Кстати, это один из тех случаев, где пригодится неизме
+няемый и не порождающий исключения конструктор по умолчанию
+`Widget.init` (см. раздел 7.1.3.1). Без него сложно было бы найти способ ос
+тавить источник перемещения в строго определенном пустом состоянии.
+
+#### 7.1.3.6. Уничтожение объекта и освобождение памяти
+
+Структура может определять деструктор с именем `~this()`:
+
+```d
+import std.stdio;
+
+struct S
+{
+    int x = 42;
+    ~this()
+    {
+        writeln("Структура S с содержимым ", x, " исчезает. Пока!");
+    }
+}
+
+void main()
+{
+    writeln("Создание объекта типа S.");
+    {
+        S object;
+        writeln("Внутри области видимости объекта ");
+    }
+    writeln("Вне области видимости объекта");
+}
+```
+
+Эта программа гарантированно выведет на экран:
+
+```
+Создание объекта типа S.
+Внутри области видимости объекта
+Структура S с содержимым 42 исчезает. Пока!
+Вне области видимости объекта.
+```
+
+Каждая структура обладает *временем жизни в пределах области видимости* (*scoped lifetime*), то есть ее жизнь действительно заканчивается
+с окончанием области видимости объекта. Подробнее:
+
+- время жизни нестатического объекта, определенного внутри функ
+ции, заканчивается в конце текущей области видимости (то есть
+контекста) до уничтожения всех объектов-структур, определенных
+перед ним;
+- время жизни объекта, определенного в качестве члена другой струк
+туры, заканчивается непосредственно после окончания времени жиз
+ни включающего объекта;
+- время жизни объекта, определенного в контексте модуля, бесконеч
+но; если вам нужно вызвать деструктор этого объекта, сделайте это
+в деструкторе модуля (см. раздел 11.3);
+- время жизни объекта, определенного в качестве члена класса, за
+канчивается в тот момент, когда сборщик мусора забирает память
+включающего объекта.
+
+Язык гарантирует автоматический вызов деструктора `~this` по оконча
+нии времени жизни объекта-структуры, что очень удобно, если вы хо
+тите автоматически выполнять такие операции, как закрытие файлов
+и освобождение всех важных ресурсов.
+
+Оригинал копии, использующей конструктор копирования, подчиня
+ется обычным правилам для времени жизни, но деструктор оригинала
+копии, полученной перемещением «сырой» памяти без вызова `this(this)`,
+не вызывается.
+
+Освобождение памяти объекта-структуры по идее выполняется сразу
+же после деструкции.
+
+#### 7.1.3.7. Алгоритм уничтожения структуры
+
+По умолчанию объекты-структуры уничтожаются в порядке, строго
+обратном порядку их создания. То есть первым уничтожается объект-
+структура, определенный в заданной области видимости последним:
+
+```d
+import std.conv, std.stdio;
+
+struct S
+{
+    private string name;
+    this(string name)
+    {
+        writeln(name, " создан.");
+        this.name = name;
+    }
+    ~this()
+    {
+        writeln(name, " уничтожен.");
+    }
+}
+
+void main()
+{
+    auto obj1 = S("первый объект");
+    foreach (i; 0 .. 3)
+    {
+        auto obj = S(text("объект ", i));
+    }
+    auto obj2 = S("последний объект");
+}
+```
+
+Эта программа выведет на экран:
+
+```
+первый объект создан.
+объект 0 создан.
+объект 0 уничтожен.
+объект 1 создан.
+объект 1 уничтожен.
+объект 2 создан.
+объект 2 уничтожен.
+последний объект создан.
+последний объект уничтожен.
+первый объект уничтожен.
+```
+
+Как и ожидалось, объект, созданный первым, был уничтожен послед
+ним. На каждой итерации цикл входит в контекст и выходит из контек
+ста управляемой инструкции.
+
+Можно явно инициировать вызов деструктора объекта-структуры с по
+мощью инструкции `clear(объект);`. С функцией `clear` мы уже познакоми
+лись в разделе 6.3.5. Тогда она оказалась полезной для уничтожения
+состояния объекта-класса. Для объектов-структур функция `clear` дела
+ет то же самое: вызывает деструктор, а затем копирует биты значения
+`.init` в область памяти объекта. В результате получается правильно
+сконструированный объект, правда, без какого-либо интересного содер
+жания.
+
+### 7.1.4. Статические конструкторы и деструкторы
+
+Структура может определять любое число статических конструкторов
+и деструкторов. Это средство полностью идентично одноименному сред
+ству для классов, с которым мы уже встречались в разделе 6.3.6.
+
+```d
+import std.stdio;
+
+struct A
+{
+    static ~this()
+    {
+        writeln("Первый статический деструктор");
+    }
+    ...
+    static this()
+    {
+        writeln("Первый статический конструктор ");
+    }
+    ...
+    static this()
+    {
+        writeln("Второй статический конструктор");
+    }
+    ...
+    static ~this()
+    {
+        writeln("Второй статический деструктор");
+    }
+}
+
+void main()
+{
+    writeln("Внимание, говорит main");
+}
+```
+
+Парность статических конструкторов и деструкторов не требуется. Под
+система поддержки времени исполнения не делает ничего интересно
+го – просто выполняет все статические конструкторы перед вычислени
+ем функции `main` в порядке их определения. По завершении выполне
+ния `main` подсистема поддержки времени исполнения так же скучно вы
+зывает все статические деструкторы в порядке, обратном порядку их
+определения. Предыдущая программа выведет на экран:
+
+```
+Первый статический конструктор
+Второй статический конструктор
+Внимание, говорит main
+Второй статический деструктор
+Первый статический деструктор
+```
+
+Порядок выполнения очевиден для статических конструкторов и де
+структоров, расположенных внутри одного модуля, но в случае не
+скольких модулей не всегда все так же ясно. Порядок выполнения ста
+тических конструкторов и деструкторов из разных модулей определен
+в разделе 6.3.6.
+
+### 7.1.5. Методы
+
+Структуры могут определять функции-члены, также называемые мето
+дами. Поскольку в случае структур о наследовании и переопределении
+речи нет, методы структур лишь немногим больше, чем функции.
+
+Нестатические методы структуры `S` принимают скрытый параметр `this`
+по ссылке (эквивалент параметра `ref S`). Поиск имен внутри методов
+структуры производится так же, как и внутри методов класса: парамет
+ры перекрывают одноименные внутренние элементы структуры, а име
+на внутренних элементов структуры перекрывают те же имена, объяв
+ленные на уровне модуля.
+
+```d
+void fun(int x)
+{
+    assert(x != 0);
+}
+
+// Проиллюстрируем правила поиска имен
+struct S
+{
+    int x = 1;
+    static int y = 324;
+
+    void fun(int x)
+    {
+        assert(x == 0);      // Обратиться к параметру x
+        assert(this.x == 1); // Обратиться к внутреннему элементу x
+    }
+
+    void gun()
+    {
+        fun(0);  // Вызвать метод fun
+        .fun(1); // Вызвать функцию fun, определенную на уровне модуля
+    }
+
+    // Тесты модуля могут быть внутренними элементами структуры
+    unittest
+    {
+        S obj;
+        obj.gun();
+        assert(y == 324);    // Тесты модуля, являющиеся "внутренними элементами", видят статические данные
+    }
+}
+```
+
+Кроме того, в этом примере есть тест модуля, определенный внутри
+структуры. Такие тесты модуля, являющиеся «внутренними элемента
+ми», не наделены никаким особым статусом, но их очень удобно встав
+лять после каждого определения метода. Коду тела внутреннего теста
+модуля доступна та же область видимости, что и обычным статическим
+методам: например, тесту модуля в предыдущем примере не требуется
+снабжать статическое поле y префиксом `S`, как это не потребовалось бы
+любому методу структуры.
+
+Некоторые особые методы заслуживают более тщательного рассмотре
+ния. К ним относятся оператор присваивания `opAssign`, используемый
+оператором `=`, оператор равенства `opEquals`, используемый операторами
+`==` и `!=`, а также упорядочивающий оператор `opCmp`, используемый опера
+торами `<`, `<=`, `>=` и `>`. На самом деле, эта тема относится к главе 12, так как
+затрагивает вопрос перегрузки операторов, но эти операторы особен
+ные: компилятор может сгенерировать их автоматически, со всем их
+особым поведением.
+
+#### 7.1.5.1. Оператор присваивания
+
+По умолчанию, если задать:
+
+```d
+struct Widget { ... } // Определен так же, как в разделе 7.1.3.4
+Widget w1, w2;
+...
+w1 = w2;
+```
+
+то присваивание делается через копирование всех внутренних элемен
+тов по очереди. В случае типа `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`:
+
+```d
+struct Widget
+{
+    private int[] array;
+    ... // this(uint), this(this), и т. д.
+    ref Widget opAssign(ref Widget rhs)
+    {
+        array = rhs.array.dup;
+        return this;
+    }
+}
+```
+
+Оператор присваивания возвращает ссылку на `this`, тем самым позво
+ляя создавать цепочки присваиваний а-ля `w1 = w2 = w3`, которые компиля
+тор заменяет на `w1.opAssign(w2.opAssign(w3))`.
+
+Осталась одна проблема. Рассмотрим присваивание:
+
+```d
+Widget w;
+...
+w = Widget(50); // Ошибка! Невозможно привязать r-значение типа Widget к ссылке ref Widget!
+```
+
+Проблема в том, что метод `opAssign` в таком виде, в каком он определен
+сейчас, ожидает аргумент типа `ref Widget`, то есть l-значение типа `Widget`.
+Чтобы помимо l-значений можно было бы присваивать еще и r-значе
+ния, структура `Widget` должна определять *два* оператора присваивания:
+
+```d
+import std.algorithm;
+
+struct Widget
+{
+    private int[] array;
+    ... // this(uint), this(this), и т. д.
+    ref Widget opAssign(ref Widget rhs)
+    {
+        array = rhs.array.dup;
+        return this;
+    }
+    ref Widget opAssign(Widget rhs)
+    {
+        swap(array, rhs.array);
+        return this;
+    }
+}
+```
+
+В версии метода, принимающей r-значения, уже отсутствует обраще
+ние к свойству `.dup`. Почему? Ну, r-значение (а с ним и его массив) – это
+практически собственность второго метода `opAssign`: оно было скопиро
+вано перед входом в функцию и будет уничтожено сразу же после того,
+как функция вернет управление. Это означает, что больше нет нужды
+дублировать `rhs.array`, потому что его потерю никто не ощутит. Доста
+точно лишь поменять местами `rhs.array` и `this.array`. Функция `opAssign`
+возвращает результат, и `rhs` и старый массив объекта `this` уходят в ни
+куда, а `this` остается с массивом, ранее принадлежавшим `rhs`, – совер
+шенное сохранение состояния.
+
+Теперь можно совсем убрать первую перегруженную версию оператора
+`opAssign`: та версия, что принимает `rhs` по значению, заботится обо всем
+сама (l-значения автоматически конвертируются в r-значения). Но оста
+вив версию с l-значением, мы сохраняем точку, через которую можно оп
+тимизировать работу оператора присваивания. Вместо того чтобы дуб
+лировать структуру-оригинал с помощью свойства `.dup`, метод `opAssign`
+может проверять, достаточно ли в текущем массиве места для размеще
+ния нового содержимого, и если да, то достаточно и записи поверх ста
+рого массива на том же месте.
+
+```d
+// Внутри Widget ...
+ref Widget opAssign(ref Widget rhs)
+{
+    if (array.length < rhs.array.length)
+    {
+        array = rhs.array.dup;
+    }
+    else
+    {
+        // Отрегулировать длину
+        array.length = rhs.array.length;
+        // Скопировать содержимое массива array (см. раздел 4.1.7)
+        array[] = rhs.array[];
+    }
+    return this;
+}
+```
+
+#### 7.1.5.2. Сравнение структур на равенство
+
+Средство для сравнения объектов-структур предоставляется «в комп
+лекте» – это операторы `==` и `!=`. Сравнение представляет собой поочеред
+ное сравнение внутренних элементов объектов и возвращает `false`, если
+хотя бы два соответствующих друг другу элемента сравниваемых объ
+ектов не равны, иначе результатом сравнения является `true`.
+
+```d
+struct Point
+{
+    int x, y;
+}
+
+unittest
+{
+    Point a, b;
+    assert(a == b);
+    a.x = 1;
+    assert(a != b);
+}
+```
+
+Чтобы определить собственный порядок сравнения, определите метод
+`opEquals`:
+
+```d
+import std.math, std.stdio;
+
+struct Point
+{
+    float x = 0, y = 0;
+    // Добавлено
+    bool opEquals(ref const Point rhs) const
+    {
+        // Выполнить приблизительное сравнение
+        return approxEqual(x, rhs.x) && approxEqual(y, rhs.y);
+    }
+}
+
+unittest
+{
+    Point a, b;
+    assert(a == b);
+    a.x = 1e-8;
+    assert(a == b);
+    a.y = 1e-1;
+    assert(a != b);
+}
+```
+
+По сравнению с методом `opEquals` для классов (см. раздел 6.8.3) метод
+`opEquals` для структур гораздо проще: ему не нужно беспокоиться о кор
+ректности своих действий из-за наследования. Компилятор попросту
+заменяет сравнение объектов-структур на вызов метода `opEquals`. Ко
+нечно, применительно к структурам остается требование определять
+осмысленный метод `opEquals`: рефлексивный, симметричный и транзи
+тивный. Заметим, что хотя метод `Point.opEquals` выглядит довольно ос
+мысленно, он не проходит тест на транзитивность. Лучшим вариантом
+оператора сравнения на равенство было бы сравнение двух объектов ти
+па `Point`, значения координат которых предварительно усечены до сво
+их старших разрядов. Такую проверку было бы гораздо проще сделать
+транзитивной.
+
+Если структура содержит внутренние элементы, определяющие мето
+ды `opEquals`, а сама такой метод не определяет, при сравнении все равно
+будут вызваны существующие методы `opEquals` внутренних элементов.
+Продолжим работать с примером, содержащим структуру `Point`:
+
+```d
+struct Rectangle
+{
+    Point leftBottom, rightTop;
+}
+
+unittest
+{
+    Rectangle a, b;
+    assert(a == b);
+    a.leftBottom.x = 1e-8;
+    assert(a == b);
+    a.rightTop.y = 5;
+    assert(a != b);
+}
+```
+
+Для любых двух объектов `a` и `b` типа `Rectangle` вычисление `a == b` эквива
+лентно вычислению выражения
+
+```d
+a.leftBottom == b.leftBottom && a.rightTop == b.rightTop
+```
+
+что в свою очередь можно переписать так:
+
+```d
+a.leftBottom.opEquals(b.leftBottom) && a.rightTop.opEquals(b.rightTop)
+```
+
+Этот пример также показывает, что сравнение выполняется в порядке
+объявления полей (т. е. поле `leftBottom` проверяется до проверки `rightTop`), и если встретились два неравных поля, сравнение завершается до
+того, как будут проверены все поля, благодаря сокращенному вычисле
+нию логических связок, построенных с помощью оператора `&&` (short
+circuit evaluation).
+
+### 7.1.6. Статические внутренние элементы
+
+Структура может определять статические данные и статические внут
+ренние функции. Помимо ограниченной видимости и подчинения пра
+вилам доступа (см. раздел 7.1.7) режим работы статических внутренних
+функций ничем не отличается от режима работы обычных функций.
+Нет скрытого параметра `this`, не вовлечены никакие другие особые ме
+ханизмы.
+
+Точно так же статические данные схожи с глобальными данными,
+определенными на уровне модуля (см. раздел 5.2.4), во всем, кроме ви
+димости и ограничений доступа, наложенных на эти статические дан
+ные родительской структурой.
+
+```d
+import std.stdio;
+
+struct Point
+{
+    private int x, y;
+    private static string formatSpec = "(%s %s)\n";
+    static void setFormatSpec(string newSpec)
+    {
+        ... // Проверить корректность спецификации формата
+        formatSpec = newSpec;
+    }
+
+    void print()
+    {
+        writef(formatSpec, x, y);
+    }
+}
+
+void main()
+{
+    auto pt1 = Point(1, 2);
+    pt1.print();
+    // Вызвать статическую внутреннюю функцию, указывая ее принадлежность префиксом Point или pt1
+    Point.setFormatSpec("[%s, %s]\n");
+    auto pt2 = Point(5, 3);
+    // Новая спецификация действует на все объекты типа Point
+    pt1.print();
+    pt2.print();
+}
+```
+
+Эта программа выведет на экран:
+
+```
+(1 2)
+[1, 2]
+[5, 3]
+```
+
+### 7.1.7. Спецификаторы доступа
+
+Структуры подчиняются спецификаторам доступа `private` (см. раз-
+дел 6.7.1), `package` (см. раздел 6.7.2), `public` (см. раздел 6.7.4) и `export` (см.
+раздел 6.7.5) тем же образом, что и классы. Спецификатор `protected`
+применительно к структурам не имеет смысла, поскольку структуры
+не поддерживают наследование.
+
+За подробной информацией обратитесь к соответствующим разделам.
+А здесь мы лишь вкратце напомним смысл спецификаторов:
+
+```d
+struct S
+{
+    private int a; // Доступен в пределах текущего файла и в методах S
+    package int b; // Доступен в пределах каталога текущего файла
+    public int c; // Доступен в пределах текущего приложения
+    export int d; // Доступен вне текущего приложения (там, где оно используется)
+}
+```
+
+Заметим, что хотя ключевое слово `export` разрешено везде, где синтак
+сис допускает применение спецификатора доступа, семантика этого
+ключевого слова зависит от реализации.
+
+### 7.1.8. Вложенность структур и классов
+
+Часто бывает удобно вложить в структуру другую структуру или класс.
+Например, контейнер дерева можно представить как оболочку-струк
+туру с простым интерфейсом поиска, а внутри нее для определения уз
+лов дерева использовать полиморфизм.
+
+```d
+struct Tree
+{
+private:
+    class Node
+    {
+        int value;
+        abstract Node left();
+        abstract Node right();
+    }
+    class NonLeaf : Node
+    {
+        Node _left, _right;
+        override Node left() { return _left; }
+        override Node right() { return _right; }
+    }
+    class Leaf : Node
+    {
+        override Node left() { return null; }
+        override Node right() { return null; }
+    }
+    // Данные
+    Node root;
+public:
+    void add(int value) { ... }
+    bool search(int value) { ... }
+}
+```
+
+Аналогично структура может быть вложена в другую структуру...
+
+```d
+struct Widget
+{
+private:
+    struct Field
+    {
+        string name;
+        uint x, y;
+    }
+    Field[] fields;
+public:
+    ...
+}
+```
+
+...и наконец, структура может быть вложена в класс.
+
+```d
+class Window
+{
+    struct Info
+    {
+        string name;
+        Window parent;
+        Window[] children;
+    }
+    Info getInfo();
+    ...
+}
+```
+
+В отличие от классов, вложенных в другие классы, вложенные струк
+туры и классы, вложенные в другие структуры, не обладают никаким
+скрытым внутренним элементом `outer` – никакой специальный код не
+генерируется. Такие вложенные типы определяются в основном со
+структурной целью – чтобы получить нужное управление доступом.
+
+### 7.1.9. Структуры, вложенные в функции
+
+Вспомним, что говорилось в разделе 6.11.1: вложенные классы находят
+ся в привилегированном положении, ведь они обладают особыми, уни
+кальными свойствами. Вложенному классу доступны параметры и ло
+кальные переменные включающей функции. Если вы возвращаете вло
+женный класс в качестве результата функции, компилятор даже разме
+щает кадр функции в динамической памяти, чтобы параметры
+и локальные переменные функции выжили после того, как она вернет
+управление.
+
+Для единообразия и согласованности D оказывает структурам, вложен
+ным в функции, те же услуги, что и классам, вложенным в функции.
+Вложенная структура может обращаться к параметрам и локальным
+переменным включающей функции:
+
+```d
+void fun(int a)
+{
+    int b;
+    struct Local
+    {
+        int c;
+        int sum()
+        {
+            // Обратиться к параметру, переменной и собственному внутреннему элементу структуры Local
+            return a + b + c;
+        }
+    }
+    Local obj;
+    int x = obj.sum();
+    // (void*).sizeof – размер указателя на окружение
+    // int.sizeof – размер единственного поля структуры
+    assert(Local.sizeof == (void*).sizeof + int.sizeof);
+}
+
+unittest
+{
+    fun(5);
+}
+```
+
+Во вложенные структуры встраивается волшебный «указатель на кадр»,
+с помощью которого они получают доступ к внешним значениям, та
+ким как `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`:
+
+```d
+import std.stdio;
+
+class Widget
+{
+    void print()
+    {
+        writeln("Привет, я объект класса Widget. Вот, пожалуй, и все обо мне.");
+    }
+}
+
+unittest
+{
+    auto a = Final!Widget(new Widget);
+    a.print();      // Все в порядке, просто печатаем a
+    auto b = a;     // Все в порядке, a и b привязаны к одному и тому же объекту типа Widget
+    a = b;          // Ошибка! opAssign(Final!Widget) деактивизирован!
+    a = new Widget; // Ошибка! Невозможно присвоить значение r-значению, возвращенному функцией get()!
+}
+```
+
+Предназначение типа `Final` – быть особым видом ссылки на класс, раз
+и навсегда привязанной к одному объекту. Такие «преданные» ссылки
+полезны для реализации множества проектных идей.
+
+Первый шаг – избавиться от присваивания. Проблема в том, что опера
+тор присваивания генерируется автоматически, если не объявлен поль
+зователем, поэтому структура `Final` должна вежливо указать компиля
+тору не делать этого. Для этого предназначен атрибут `@disable`:
+
+```d
+struct Final(T)
+{
+    // Запретить присваивание
+    @disable void opAssign(Final);
+    ...
+}
+```
+
+С помощью атрибута `@disable` можно запретить и другие сгенерирован
+ные функции, например сравнение.
+
+До сих пор все шло хорошо. Чтобы реализовать `Final!T`, нужно с помо
+щью конструкции `alias this` сделать `Final(T)` подтипом `T`, но чтобы при
+этом полученный тип не являлся l-значением. Ошибочное решение вы
+глядит так:
+
+```d
+// Ошибочное решение
+struct Final(T)
+{
+    private T payload;
+    this(T bindTo)
+    {
+        payload = bindTo;
+    }
+    // Запретить присваивание
+    @disable void opAssign(Final);
+    // Сделать Final(T) подклассом T
+    alias payload this;
+}
+```
+
+Структура `Final` хранит ссылку на себя в поле `payload`, которое инициа
+лизируется в конструкторе. Кроме того, объявив, но не определяя ме
+тод `opAssign`, структура эффективно «замораживает» присваивание. Та
+ким образом, клиентский код, пытающийся присвоить значение объек
+ту типа `Final!T`, или не сможет обратиться к `payload` (из-за `private`), или
+получит ошибку во время компоновки.
+
+Ошибка `Final` – в использовании инструкции `alias payload this;`. Этот
+тест модуля делает что-то непредусмотренное:
+
+```d
+class A
+{
+    int value = 42;
+    this(int x) { value = x; }
+}
+
+unittest
+{
+    auto v = Final!A(new A(42));
+    void sneaky(ref A ra)
+    {
+        ra = new A(4242);
+    }
+    sneaky(v); // Хм-м-м...
+    assert(v.value == 4242); // Проходит?!?
+}
+```
+
+`alias payload this` действует довольно просто: каждый раз, когда значе
+ние `объект` типа `Final!T` используется в недопустимом для этого типа кон
+тексте, компилятор вместо `объект` пишет `объект.payload` (то есть делает
+`объект.payload` *псевдонимом* для `объекта` в соответствии с именем и син
+таксисом конструкции `alias`). Но выражение `объект.payload` представля
+ет собой непосредственное обращение к полю `объект`, следовательно, яв
+ляется l-значением. Это l-значение привязано к переданному по ссылке
+параметру функции `sneaky` и, таким образом, позволяет `sneaky` напря
+мую изменять значение поля объекта `v`.
+
+Чтобы это исправить, нужно сделать объект псевдонимом r-значения.
+Так мы получим полную функциональность, но ссылка, сохраненная
+в `payload`, станет неприкосновенной. Очень просто осуществить привяз
+ку к r-значению с помощью свойства (объявленного с атрибутом `@property`), возвращающего `payload` по значению:
+
+```d
+struct Final(T)
+{
+    private T payload;
+    this(T bindTo)
+    {
+        payload = bindTo;
+    }
+    // Запретить присваивание, оставив метод opAssign неопределенным
+    private void opAssign(Final);
+    // Сделать Final(T) подклассом T, не разрешив при этом перепривязывать payload
+    @property T get() { return payload; }
+    alias get this;
+}
+```
+
+Ключевой момент в новом определении структуры – то, что метод `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`:
+
+```d
+class Widget
+{
+    private int x;
+    @property int get() { return x; }
+}
+
+unittest
+{
+    auto w = Final!Widget(new Widget);
+    auto x = w.get; // Получает Widget из Final, а не int из Widget
+}
+```
+
+Чтобы избежать таких коллизий, воспользуемся соглашением об име
+новании. Для надежности будем просто добавлять к именам видимых
+свойств имя соответствующего типа:
+
+```d
+struct Final(T)
+{
+    private T Final_payload;
+    this(T bindTo)
+    {
+        Final_payload = bindTo;
+    }
+    // Запретить присваивание
+    @disable void opAssign(Final);
+    // Сделать Final(T) подтипом T, не разрешив при этом перепривязывать payload
+    @property T Final_get() { return Final_payload; }
+    alias Final_get this;
+}
+```
+
+Соблюдение такого соглашения сводит к минимуму риск непредвиден
+ных коллизий. (Конечно, иногда можно намеренно перехватывать не
+которые методы, оставив вызовы к ним за перехватчиком.)
+
+#### 7.1.11. Взаимное расположение полей. Выравнивание
+
+Как располагаются поля в объекте-структуре? D очень консервативен
+в отношении структур: он располагает элементы их содержимого в том
+же порядке, в каком они указаны в определении структуры, но сохра
+няет за собой право вставлять между полями *отступы* (*padding*). Рас
+смотрим пример:
+
+```d
+struct A
+{
+    char a;
+    int b;
+    char c;
+}
+```
+
+Если бы компилятор располагал поля в точном соответствии с размера
+ми, указанными в структуре `A`, то адресом поля `b` оказался бы адрес
+объекта `A` плюс 1 (поскольку поле `a` типа `char` занимает ровно 1 байт). Но
+такое расположение проблематично, ведь современные компьютерные
+системы извлекают данные только блоками по 4 или 8 байт, то есть мо
+гут извлекать только данные, расположенные по адресам, кратным 4
+и 8 соответственно. Предположим, объект типа `A` расположен по «хоро
+шему» адресу, например кратному 8. Тогда адрес поля `b` точно окажется
+не в лучшем районе города. Чтобы извлечь `b`, процессору придется пово
+зиться, ведь нужно будет «склеивать» значение `b`, собирая его из кусоч
+ков размером в байт. Усугубляет ситуацию то, что в зависимости от
+компилятора и низкоуровневой архитектуры аппаратного обеспечения
+эта операция сборки может быть выполнена лишь в ответ на прерыва
+ние ядра «обращение к невыровненным данным», обработка которого
+требует своих (и немалых) накладных расходов. А это вам не семеч
+ки щелкать: такая дополнительная гимнастика легко снижает скорость
+доступа на несколько порядков.
+
+Вот почему современные компиляторы располагают данные в памяти
+с *отступами*. Компилятор вставляет в объект дополнительные байты,
+чтобы обеспечить расположение всех полей с удобными смещениями.
+Таким образом, выделение под объекты областей памяти с адресами,
+кратными слову, гарантирует быстрый доступ ко всем внутренним эле
+ментам этих объектов. На рис. 7.2 показано расположение полей типа `A`
+по схеме с отступами.
+
+![image-7-1-11](images/image-7-1-11.png)
+
+***Рис. 7.2.*** *Расположение полей типа `A` по схеме с отступами. Заштрихованные области – это отступы, вставленные для правильного выравнивания. Компилятор вставляет в объект две лакуны, тем самым добавляя 6 байт простаивающего места или 50% общего размера объекта*
+
+Полученное расположение полей характеризуется обилием отступов (за
+штрихованных областей). В случае классов компилятор волен упорядо
+чивать поля по собственному усмотрению, но при работе со структурой
+есть смысл позаботиться о расположении данных, если объем исполь
+зуемой памяти имеет значение. Лучше всего расположить поле типа int
+первым, а после него – два поля типа `char`. При таком порядке полей
+структура займет 64 бита, включая 2 байта отступа.
+
+Каждое из полей объекта обладает известным во время компиляции сме
+щением относительно начального адреса объекта. Это смещение всегда
+одинаково для всех объектов заданного типа в рамках одной программы
+(оно может меняться от компиляции к компиляции, но не от запуска
+к запуску). Смещение доступно пользовательскому коду как значение
+свойства `.offsetof`, неявно определенного для каждого поля класса или
+структуры:
+
+```d
+import std.stdio;
+
+struct A
+{
+    char a;
+    int b;
+    char c;
+}
+
+void main()
+{
+    A x;
+    writefln("%s %s %s", x.a.offsetof, x.b.offsetof, x.c.offsetof);
+}
+```
+
+Эталонная реализация компилятора выведет `0 4 8`, открывая схему рас
+положения полей, которую мы уже видели на рис. 7.2. Не совсем удоб
+но, что для доступа к некоторой статической информации о типе `A` при
+ходится создавать объект этого типа, но синтаксис `A.a.offsetof` не ком
+пилируется. Здесь поможет такой трюк: выражение `A.init.a.offsetof`
+позволяет получить смещение для любого внутреннего элемента струк
+туры в виде константы, известной во время компиляции.
+
+```d
+import std.stdio;
+
+struct A
+{
+    char a;
+    int b;
+    char c;
+}
+
+void main()
+{
+    // Получить доступ к смещениям полей, не создавая объект
+    writefln("%s %s %s", A.init.a.offsetof,
+    A.init.b.offsetof, A.init.c.offsetof);
+}
+```
+
+D гарантирует, что все байты отступов последовательно заполняются
+нулями.
+
+#### 7.1.11.1. Атрибут align
+
+Чтобы перекрыть выбор компилятора, определив собственное выравни
+вание, что повлияет на вставляемые отступы, объявляйте поля с атрибу
+том `align`. Такое переопределение может понадобиться для взаимодейст
+вия с определенной аппаратурой или для работы по бинарному протоко
+лу, задающему особое выравнивание. Пример атрибута `align` в действии:
+
+```d
+class A
+{
+    char a;
+    align(1) int b;
+    char c;
+}
+```
+
+При таком определении поля структуры `A` располагаются без пустот
+между ними. (В конце объекта при этом может оставаться зарезервиро
+ванное, но не занятое место.) Аргумент атрибута `align` означает *максимальное* выравнивание поля, но реальное выравнивание не может пре
+высить естественное выравнивание для типа этого поля. Получить ес
+тественное выравнивание типа `T` позволяет определенное компилятором
+свойство `T.alignof`. Если вы, например, укажете для `b` выравнивание
+align(200) вместо указанного в примере `align(1)`, то реально выравнива
+ние примет значение `4`, равное `int.alignof`.
+
+Атрибут `align` можно применять к целому классу или структуре:
+
+```d
+align(1) struct A
+{
+    char a;
+    int b;
+    char c;
+}
+```
+
+Для структуры атрибут `align` устанавливает выравнивание по умолча
+нию заданным значением. Это умолчание можно переопределить инди
+видуа льными атрибутами `align` внутри структуры. Если для поля ти
+па `T` указать только ключевое слово `align` без числа, компилятор прочи
+тает это как `align(T.alignof)`, то есть такая запись переустанавливает
+выравнивание поля в его естественное значение.
+
+Атрибут `align` не предназначен для использования с указателями и ссыл
+ками. Сборщик мусора действует из расчета, что все ссылки и указатели
+выровнены по размеру типа `size_t`. Компилятор не настаивает на со
+блюдении этого ограничения, поскольку в общем случае у вас могут
+быть указатели и ссылки, не контролируемые сборщиком мусора. Та
+ким образом, следующее определение крайне опасно, поскольку компи
+лируется без предупреждений:
+
+```d
+struct Node
+{
+    short value;
+    align(2) Node* next; // Избегайте таких определений
+}
+```
+
+Если этот код выполнит присваивание `объект.next = new Node` (то есть за
+полнит `объект.next` ссылкой, контролируемой сборщиком мусора), хаос
+обеспечен: неверно выровненная ссылка пропадает из поля зрения сбор
+щика мусора, память может быть освобождена, и `объект.next` превраща
+ется в «висячий» указатель.
+
+## 7.2. Объединение
+
+Объединения в стиле C можно использовать и в D, но не забывайте, что
+делать это нужно редко и с особой осторожностью.
+
+Объединение (`union`) – это что-то вроде структуры, все внутренние поля
+которой начинаются по одному и тому же адресу. Таким образом, их об
+ласти памяти перекрываются, а это значит, что именно вы как пользо
+ватель объединения отвечаете за соответствие записываемой и считы
+ваемой информации: нужно всегда читать в точности тот тип, который
+был записан. В любой конкретный момент времени только один внут
+ренний элемент объединения обладает корректным значением.
+
+```d
+union IntOrFloat
+{
+    int _int;
+    float _float;
+}
+
+unittest
+{
+    IntOrFloat iof;
+    iof._int = 5;
+    // Читать только iof._int, но не iof._float
+    assert(iof._int == 5);
+    iof._float = 5.5;
+    // Читать только iof._float, но не iof._int
+    assert(iof._float == 5.5);
+}
+```
+
+Поскольку типы `int` и `float` имеют строго один и тот же размер (4 байта),
+внутри объединения `IntOrFloat` их области памяти в точности совпадают.
+Но детали их расположения не регламентированы, например, пред
+ставления `_int` и `_float` могут отличаться порядком хранения байтов:
+старший байт `_int` может иметь наименьший адрес, а старший байт
+`_float` (тот, что содержит знак и большую часть показателя степени) –
+наибольший адрес.
+
+Объединения не помечаются, то есть сам объект типа `union` не содержит
+«метки», которая служила бы средством, позволяющим определять,
+какой из внутренних элементов является «хорошим». Ответственность
+за корректное использование объединения целиком ложится на плечи
+пользователя, что делает объединения довольно неприятным средством
+при построении более крупных абстракций.
+
+В определенном, но неинициализированном объекте типа `union` уже
+есть одно инициализированное поле: первое поле автоматически ини
+циализируется соответствующим значением `.init`, поэтому оно доступ
+но для чтения сразу по завершении построения по умолчанию. Чтобы
+инициализировать первое поле значением, отличным от `.init`, укажите
+нужное инициализирующее выражение в фигурных скобках:
+
+```d
+unittest
+{
+    IntOrFloat iof = { 5 };
+    assert(iof._int == 5);
+}
+```
+
+В статическом объекте типа `union` может быть инициализировано и дру
+гое поле. Для этого используйте следующий синтаксис:
+
+```d
+unittest
+{
+    static IntOrFloat iof = { _float : 5 };
+    assert(iof._float == 5);
+}
+```
+
+Следует отметить, что нередко объединение служит именно для того,
+чтобы считывать тип, отличный от исходно записанного, – в соответст
+вии с порядком управления представлением, принятым в некоторой
+системе. По этой причине компилятор не выявляет даже те случаи не
+корректного использования объединений, которые может обнаружить.
+Например, на 32-разрядной машине Intel следующий код компилиру
+ется и даже выполнение инструкции `assert` не порождает исключений:
+
+```
+unittest
+{
+    IntOrFloat iof;
+    iof._float = 1;
+    assert(iof._int == 0x3F80_0000);
+}
+```
+
+Объединение может определять функции-члены и, в общем случае, лю
+бые из тех внутренних элементов, которые может определять структу
+ра, за исключением конструкторов и деструкторов.
+
+Чаще всего (точнее, наименее редко) объединения используются в каче
+стве анонимных членов структур. Например:
+
+```d
+import std.contracts;
+
+struct TaggedUnion
+{
+    enum Tag { _tvoid, _tint, _tdouble, _tstring, _tarray }
+    private Tag _tag;
+    private union
+    {
+        int _int;
+        double _double;
+        string _string;
+        TaggedUnion[] _array;
+    }
+public:
+    void opAssign(int v)
+    {
+        _int = v;
+        _tag = Tag._tint;
+    }
+    int getInt()
+    {
+        enforce(_tag == Tag._tint);
+        return _int;
+    }
+    ...
+}
+
+unittest
+{
+    TaggedUnion a;
+    a = 4;
+    assert(a.getInt() == 4);
+}
+```
+
+(Подробно тип `enum` описан в разделе 7.3.)
+
+Этот пример демонстрирует чисто классический способ использования
+`union` в качестве вспомогательного средства для определения так назы
+ваемого *размеченного объединения* (*discriminated union*, *tagged union*),
+также известного как алгебраический тип. Размеченное объединение
+инкапсулирует небезопасный объект типа `union` в «безопасной коробке»,
+которая отслеживает последний присвоенный тип. Сразу после ини
+циализации поле `Tag` содержит значение `Tag._tvoid`, по сути означающее,
+что объект не инициализирован. При присваивании объединению неко
+торого значения срабатывает оператор `opAssign`, устанавливающий тип
+объекта в соответствии с типом присваиваемого значения. Чтобы полу
+чить законченную реализацию, потребуется определить методы `opAssign(double)`, `opAssign(string)` и `opAssign(TaggedUnion[])` с соответствующи
+ми функциями `getXxx()`.
+
+Внутренний элемент типа `union` анонимен, то есть одновременно являет
+ся и определением типа, и определением внутреннего элемента. Память
+под анонимное объединение выделяется как под обычный внутренний
+элемент структуры, и внутренние элементы этого объединения напря
+мую видимы внутри структуры (как показывают методы `TaggedUnion`).
+В общем случае можно определять как анонимные структуры, так и ано
+нимные объединения, и вкладывать их как угодно.
+
+В конце концов вы должны понять, что объединение не такое уж зло,
+каким может показаться. Как правило, использовать объединение вме
+сто того, чтобы играть типами с помощью выражения `cast`, – хороший
+тон в общении между программистом и компилятором. Объединение
+указателя и целого числа указывает сборщику мусора, что ему следует
+быть осторожнее и не собирать этот указатель. Если вы сохраните ука
+затель в целом числе и будете время от времени преобразовывать его на
+зад к типу указателя (с помощью `cast`), результаты окажутся непред
+сказуемыми, ведь сборщик мусора может забрать память, ассоцииро
+ванную с этим тайным указателем.
+
+## 7.3. Перечисляемые значения
+
+Типы, принимающие всего несколько определенных значений, оказа
+лись очень полезными – настолько полезными, что язык Java после не
+скольких лет героических попыток эмулировать перечисляемые типы
+с помощью идиомы в конце концов добавил их к основным типам [8].
+Определить хорошие перечисляемые типы непросто – в C (и особенно
+в C++) типу `enum` присущи свои странности. D попытался учесть пред
+шествующий опыт, определив простое и полезное средство для работы
+с перечисляемыми типами.
+
+Начнем с азов. Простейший способ применить `enum` – как сказать «да
+вайте перечислим несколько символьных значений», не ассоциируя их
+с новым типом:
+
+```d
+enum
+    mega = 1024 * 1024,
+    pi = 3.14,
+    euler = 2.72,
+    greet = "Hello";
+```
+
+С `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`:
+
+```d
+auto x = pi;         // Все в порядке, x обладает типом double
+auto y = pi * euler; // Все в порядке, y обладает типом double
+euler = 2.73;        // Ошибка! Невозможно изменить перечисляемое значение!
+void fun(ref double x) {
+...
+}
+fun(pi);             // Ошибка! Невозможно получить адрес 3.14!
+```
+
+Как показано выше, типы перечисляемых значений не ограничиваются
+типом `int` – типы `double` и `string` также допустимы. Какие вообще типы
+можно использовать с `enum`? Ответ прост: c `enum` можно использовать лю
+бой основной тип и любую структуру. Есть лишь два требования к ини
+циализирующему значению при определении перечисляемых значений:
+
+- инициализирующее значение должно быть вычислимым во время
+компиляции;
+- тип инициализирующего значения должен позволять копирование,
+то есть в его определении не должно быть `@disable this(this)` (см. раз-
+дел 7.1.3.4).
+
+Первое требование гарантирует независимость перечисляемого значе
+ния от параметров времени исполнения. Второе требование обеспечива
+ет возможность копировать значение; копия создается при каждом об
+ращении к перечисляемому значению.
+
+Невозможно определить перечисляемое значение типа `class`, поскольку
+объекты классов должны всегда создаваться с помощью оператора `new`
+(за исключением не представляющего интерес значения `null`), а выра
+жение с `new` во время компиляции вычислить невозможно. Не будет не
+ожиданностью, если в будущем это ограничение снимут или ослабят.
+
+Создадим перечисление значений типа `struct`:
+
+```d
+struct Color
+{
+    ubyte r, g, b;
+}
+
+enum
+    red = Color(255, 0, 0),
+    green = Color(0, 255, 0),
+    blue = Color(0, 0, 255);
+```
+
+Когда бы вы ни использовали, например, идентификатор `green`, код бу
+дет вести себя так, будто вместо этого идентификатора вы написали
+`Color(0, 255, 0)`.
+
+### 7.3.1. Перечисляемые типы
+
+Можно дать имя группе перечисляемых значений, создав таким обра
+зом новый тип на ее основе:
+enum OddWord { acini, alembicated, prolegomena, aprosexia }
+Члены именованной группы перечисляемых значений не могут иметь
+разные типы; все перечисляемые значения должны иметь один и тот же
+тип, поскольку пользователи могут впоследствии определять и исполь
+зовать значения этого типа. Например:
+OddWord w;
+assert(w == OddWord.acini); // Инициализирующим значением по умолчанию
+// является первое значение в множестве - acini.
+w = OddWord.aprosexia;
+// Всегда уточняйте имя значения
+// (кстати, это не то, что вы могли подумать)
+// с помощью имени типа.
+int x = w;
+// OddWord конвертируем в int, но не наоборот.
+assert(x == 3);
+// Значения нумеруются по порядку: 0, 1, 2, ...
+Тип, автоматически определяемый для поименованного перечисления, –
+int. Присвоить другой тип можно так:
+enum OddWord : byte { acini, alembicated, prolegomena, aprosexia }
+С новым определением (byte называют базовым типом OddWord) значе
+ния идентификаторов перечисления не меняются, изменяется лишь
+способ их хранения. Вы можете с таким же успехом назначить членам
+перечисления тип double или real, но связанные с идентификаторами
+значения останутся прежними: 0, 1 и т. д. Но если сделать базовым ти
+пом OddWord нечисловой тип, например string, то придется указать ини
+циализирующее значение для каждого из значений, поскольку компи
+лятору неизвестна никакая естественная последовательность, которой
+он мог бы придерживаться.
+Возвратимся к числовым перечислениям. Присвоив какому-либо члену
+перечисления особое значение, вы таким образом сбросите счетчик, ис
+пользуемый компилятором для присваивания значений идентифика
+торам. Например:
+enum E { a, b = 2, c, d = -1, e, f }
+assert(E.c == 3);
+assert(E.e == 0);
+Если два идентификатора перечисления получают одно и то же значе
+ние (как в случае с E.a и E.e), конфликта нет. Фактически равные значе
+ния можно создавать, даже не подозревая об этом – из-за непреодолимо
+го желания типов с плавающей запятой удивить небдительных пользо
+вателей:
+enum F : float { a = 1E30, b, c, d }
+assert(F.a == F.d); // Тест пройден
+Корень этой проблемы в том, что наибольшее значение типа int, кото
+рое может быть представлено значением типа float, равно 16_777_216,
+и выход за эту границу сопровождается все возрастающими диапазона
+ми целых значений, представляемых одним и тем же числом типа float.
+
+7.3.2. Свойства перечисляемых типов
+Для всякого перечисляемого типа E определены три свойства: E.init (это
+свойство принимает первое из значений, определенных в E), E.min (наи
+меньшее из определенных в E значений) и E.max (наибольшее из опреде
+ленных в E значений). Два последних значения определены, только ес
+ли базовым типом E является тип, поддерживающий сравнение во вре
+мя компиляции с помощью оператора <.
+Вы вправе определить внутри enum собственные значения min, max и init,
+но поступать так не рекомендуется: обобщенный код частенько рассчи
+тывает на то, что эти значения обладают особой семантикой.
+Один из часто задаваемых вопросов: «Можно ли добраться до имени пе
+речисляемого значения?» Вне всяких сомнений, сделать это возможно
+и на самом деле легко, но не с помощью встроенного механизма, а на ос
+нове рефлексии времени компиляции. Рефлексия работает так: с неко
+торым перечисляемым типом Enum связывается известная во время ком
+пиляции константа __traits(allMembers, Enum), которая содержит все чле
+ны Enum в виде кортежа значений типа string. Поскольку строками мож
+но манипулировать во время компиляции, как и во время исполнения,
+такой подход дает значительную гибкость. Например, немного забежав
+вперед, напишем функцию toString, которая возвращает строку, соот
+ветствующую заданному перечисляемому значению. Функция парамет
+ризирована перечисляемым типом.
+string toString(E)(E value) if (is(E == enum)) {
+foreach (s; __traits(allMembers, E)) {
+    if (value == mixin("E." ~ s)) return s;
+}
+return null;
+}
+enum OddWord { acini, alembicated, prolegomena, aprosexia }
+void main() {
+auto w = OddWord.alembicated;
+assert(toString(w) == "alembicated");
+}
+Незнакомое пока выражение 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.
+
+7.4. alias
+В ряде случаев мы уже имели дело с size_t – целым типом без знака,
+достаточно вместительным, чтобы представить размер любого объекта.
+Тип size_t не определен языком, он просто принимает форму uint или
+ulong в зависимости от адресного пространства конечной системы (32
+или 64 бита соответственно).
+Если бы вы открыли файл object.di, один из копируемых на компьютер
+пользователя (а значит, и на ваш) при инсталляции компилятора D, то
+нашли бы объявление примерно следующего вида:
+alias typeof(int.sizeof) size_t;
+Свойство .sizeof точно измеряет размер типа в байтах; в данном случае
+это тип int. Вместо int в примере мог быть любой другой тип; в данном
+случае имеет значение не указанный тип, а тип размера, возвращаемый
+оператором typeof. Компилятор измеряет размеры объектов, используя
+uint на 32-разрядных архитектурах и ulong на 64-разрядных. Следова
+тельно, конструкция alias позволяет назначить size_t синонимом uint
+или ulong.
+Обобщенный синтаксис объявления с ключевым словом alias ничуть не
+сложнее приведенного выше:
+alias ‹существующийИдентификатор› ‹новыйИдентификатор›;
+В качестве идентификатора ‹существующийИдентификатор› можно подста
+вить все, у чего есть имя. Это может быть тип, переменная, модуль – ес
+ли что-то обладает идентификатором, то для этого объекта можно соз
+дать псевдоним. Например:
+import std.stdio;
+void fun(int) {}
+void fun(string) {}
+int var;
+enum E { e }
+struct S { int x; }
+S s;
+unittest {
+alias object.Object Root; // Предок всех классов
+alias std phobos;
+// Имя пакета
+alias std.stdio io;
+// Имя модуля
+alias var sameAsVar;
+// Переменная
+alias E MyEnum;
+// Перечисляемый тип
+alias E.e myEnumValue;
+// Значение этого типа
+alias fun gun;
+// Перегруженная функция
+alias S.x field;
+// Поле структуры
+alias s.x sfield;
+// Поле объекта
+}
+Правила применения псевдонима просты: используйте псевдоним вез
+де, где допустимо использовать исходный идентификатор. Именно это
+делает компилятор, но с точностью до наоборот: он с пониманием заме
+няет идентификатор-псевдоним оригинальным идентификатором. Да
+же сообщения об ошибках и отлаживаемая программа могут «видеть
+сквозь» псевдонимы и показывать исходные идентификаторы, что мо
+жет оказаться неожиданным. Например, в некоторых сообщениях об
+ошибках или в отладочных символах можно увидеть immutable(char)[]
+вместо string. Но что именно будет показано, зависит от реализации
+компилятора.
+С помощью конструкции alias можно создавать псевдонимы псевдони
+мов для идентификаторов, уже имеющих псевдонимы. Например:
+alias int Int;
+alias Int MyInt;
+Здесь нет ничего особенного, просто следование обычным правилам:
+к моменту определения псевдонима MyInt псевдоним Int уже будет заме
+нен исходным идентификатором int, для которого Int является псевдо
+нимом.
+Конструкцию alias часто применяют, когда требуется дать сложной це
+почке идентификаторов более короткое имя или в связке с перегружен
+ными функциями из разных модулей (см. раздел 5.5.2).
+Также конструкцию alias часто используют с параметризированными
+структурами и классами. Например:
+// Определить класс-контейнер
+class Container(T) {
+alias T ElementType;
+...
+}
+unittest {
+Container!int container;
+Container!int.ElementType element;
+...
+}
+Здесь общедоступный псевдоним ElementType, созданный классом Con
+tainer, – единственный разумный способ обратиться из внешнего мира
+к аргументу, привязанному к параметру T класса Container. Идентифи
+катор T видим лишь внутри определения класса Container, но не снару
+жи: выражение Container!int.T не компилируется.
+Наконец, конструкция alias весьма полезна в сочетании с конструкци
+ей static if. Например:
+// Из файла object.di
+// Определить тип разности между двумя указателями
+static if (size_t.sizeof == 4) {
+alias int ptrdiff_t;
+} else {
+alias long ptrdiff_t;
+}
+// Использовать ptrdiff_t ...
+С помощью объявления псевдоним ptrdiff_t привязывается к разным ти
+пам в зависимости от того, по какой ветке статического условия пойдет
+поток управления. Без этой возможности привязки код, которому потре
+бовался такой тип, пришлось бы разместить в одной из веток static if.
+
+7.5. Параметризированные контексты
+(конструкция template)
+Мы уже рассмотрели средства, облегчающие параметризацию во время
+компиляции (эти средства сродни шаблонам из C++ и родовым типам из
+языков Java и C#), – это функции (см. раздел 5.3), параметризирован
+ные классы (см. раздел 6.14) и параметризированные структуры, кото
+рые подчиняются тем же правилам, что и параметризированные клас
+сы. Тем не менее иногда во время компиляции требуется каким-либо
+образом манипулировать типами, не определяя функцию, структуру
+или класс. Один из механизмов, подходящих под это описание (широко
+используемый в C++), – выбор того или иного типа в зависимости от
+статически известного логического условия. При этом не определяется
+никакой новый тип и не вызывается никакая функция – лишь создает
+ся псевдоним для одного из существующих типов.
+Для случаев, когда требуется организовать параметризацию во время
+компиляции без определения нового типа или функции, D предоставля
+ет параметризированные контексты. Такой параметризированный кон
+текст вводится следующим образом:
+template Select(bool cond, T1, T2) {
+...
+}
+Этот код – на самом деле лишь каркас для только что упомянутого меха
+низма выбора во время компиляции. Скоро мы доберемся и до реализа
+ции, а пока сосредоточимся на порядке объявления. Объявление с клю
+чевым словом template вводит именованный контекст (в данном случае
+это Select) с параметрами, вычисляемыми во время компиляции (в дан
+ном случае это логическое значение и два типа). Объявить контекст
+можно на уровне модуля, внутри определения класса, внутри определе
+ния структуры, внутри любого другого объявления контекста, но не
+внутри определения функции.
+В теле параметризированного контекста разрешается использовать все
+те же объявления, что и обычно, кроме того, могут быть использованы
+параметры контекста. Доступ к любому объявлению контекста можно
+получить извне, расположив перед его именем имя контекста и ., на
+пример: Select!(true, int, double).foo. Давайте прямо сейчас закончим
+определение контекста Select, чтобы можно было поиграть с ним:
+template Select(bool cond, T1, T2) {
+static if (cond) {
+alias T1 Type;
+} else {
+alias T2 Type;
+}
+}
+unittest {
+alias Select!(false, int, string).Type MyType;
+static assert(is(MyType == string));
+}
+Заметим, что тот же результат мы могли бы получить на основе струк
+туры или класса, поскольку эти типы могут определять в качестве сво
+их внутренних элементов псевдонимы, доступные с помощью обычного
+синтаксиса с оператором . (точка):
+struct /* или class */ Select2(bool cond, T1, T2) {
+static if (cond) {
+alias T1 Type;
+} else {
+alias T2 Type;
+}
+}
+unittest {
+alias Select2!(false, int, string).Type MyType;
+static assert(is(MyType == string));
+}
+Согласитесь, такое решение выглядит не очень привлекательно. К при
+меру, для Select2 в документации пришлось бы написать: «Не создавай
+те объекты типа Select2! Он определен только ради псевдонима внутри
+него!» Доступный специализированный механизм определения пара
+метризированных контекстов позволяет избежать двусмысленности на
+мерений, не вызывает недоумения и исключает возможность некоррект
+ного использования.
+В контексте, определенном с ключевым словом template, можно объяв
+лять не только псевдонимы – там могут присутствовать самые разные
+объявления. Определим еще один полезный шаблон. На этот раз это бу
+дет шаблон, возвращающий логическое значение, которое сообщает, яв
+ляется ли заданный тип строкой (в любой кодировке):
+template isSomeString(T) {
+enum bool value = is(T : const(char[]))
+|| is(T : const(wchar[])) || is(T : const(dchar[]));
+}
+unittest {
+// Не строки
+static assert(!isSomeString!(int).value);
+static assert(!isSomeString!(byte[]).value);
+// Строки
+static assert(isSomeString!(char[]).value);
+static assert(isSomeString!(dchar[]).value);
+static assert(isSomeString!(string).value);
+static assert(isSomeString!(wstring).value);
+static assert(isSomeString!(dstring).value);
+static assert(isSomeString!(char[4]).value);
+}
+Параметризированные контексты могут быть рекурсивными; к приме
+ру, вот одно из возможных решений задачи с факториалом:
+template factorial(uint n) {
+static if (n <= 1)
+enum ulong value = 1;
+else
+enum ulong value = factorial!(n - 1).value * n;
+}
+Несмотря на то что factorial является совершенным функциональным
+определением, в данном случае это не лучший подход. При необходимо
+сти вычислять значения во время компиляции, пожалуй, стоило бы
+воспользоваться механизмом вычислений во время компиляции (см.
+раздел 5.12). В отличие от приведенного выше шаблона factorial, функ
+ция factorial более гибка, поскольку может вычисляться как во время
+компиляции, так и во время исполнения. Конструкция template больше
+всего подходит для манипуляции типами, имеющей место в Select
+и isSomeString.
+
+7.5.1. Одноименные шаблоны
+Конструкция template может определять любое количество идентифи
+каторов, но, как видно из предыдущих примеров, нередко в ней опреде
+лен ровно один идентификатор. Обычно шаблон определяется лишь
+с целью решить единственную задачу и в качестве результата сделать
+доступным единственный идентификатор (такой как Type в случае Select
+или value в случае isSomeString).
+Необходимость помнить о том, что в конце вызова надо указать этот
+идентификатор, и всегда его указывать может раздражать. Многие про
+сто забывают добавить в конец .Type, а потом удивляются, почему вызов
+Select!(cond, A, B) порождает таинственное сообщение об ошибке.
+D помогает здесь, определяя правило, известное как фокус с одноимен
+ным шаблоном: если внутри конструкции template определен иденти
+фикатор, совпадающий с именем самого шаблона, то при любом после
+дующем использовании имени этого шаблона в его конец будет автома
+тически дописываться одноименный идентификатор. Например:
+template isNumeric(T) {
+enum bool isNumeric = is(T : long) || is(T : real);
+}
+unittest {
+static assert(isNumeric!(int));
+static assert(!isNumeric!(char[]));
+}
+Если теперь некоторый код использует выражение isNumeric!(T), компи
+лятор в каждом случае автоматически заменит его на isNumeric!(T).is
+Numeric, чем освободит пользователя от хлопот с добавлением идентифи
+катора в конец имени шаблона.
+Шаблон, проделывающий фокус с «тезками», может определять внутри
+себя и другие идентификаторы, но они будут попросту недоступны за
+пределами этого шаблона. Дело в том, что компилятор заменяет иден
+тификаторы на раннем этапе процесса поиска имен. Единственный спо
+соб получить доступ к таким идентификаторам – обратиться к ним из
+тела самого шаблона. Например:
+template isNumeric(T) {
+enum bool test1 = is(T : long);
+enum bool test2 = is(T : real);
+enum bool isNumeric = test1 || test2;
+}
+unittest {
+static assert(isNumeric!(int).test1); // Ошибка!
+// Тип bool не определяет свойство test1!
+}
+Это сообщение об ошибке вызвано соблюдением правила об одноимен
+ности: перед тем как делать что-либо еще, компилятор расширяет вызов
+isNumeric!(int) до isNumeric!(int).isNumeric. Затем пользовательский код
+делает попытку заполучить значение isNumeric!(int).isNumeric.test1, что
+равносильно попытке получить внутренний элемент test1 из логическо
+го значения, отсюда и сообщение об ошибке. Короче говоря, используй
+те одноименные шаблоны тогда и только тогда, когда хотите открыть
+доступ лишь к одному идентификатору. Этот случай скорее частый, чем
+редкий, поэтому одноименные шаблоны очень популярны и удобны.
+
+7.5.2. Параметр шаблона this1
+Познакомившись с классами и структурами, можно параметризовать
+наш обобщенный метод типом неявного аргумента this. Например:
+class Parent
+{
+static string getName(this T)()
+{
+return T.stringof;
+}
+}
+class Derived1: Parent{}
+class Derived2: Parent{}
+unittest
+{
+assert(Parent.getName() == "Parent");
+assert(Derived1.getName() == "Derived1");
+assert(Derived2.getName() == "Derived2");
+}
+Параметр шаблона this T предписывает компилятору в теле getName
+считать T псевдонимом typeof(this).
+В обычный статический метод класса не передаются никакие скрытые
+параметры, поэтому невозможно определить, для какого конкретно
+класса вызван этот метод. В приведенном примере компилятор создает
+три экземпляра шаблонного метода Parent.getName(this T)(): Parent.get
+Name(), Derived1.getName() и Derived2.getName().
+Также параметр this удобен в случае, когда один метод нужно исполь
+зовать для разных квалификаторов неизменяемости объекта (см. гла
+ву 8).
+
+7.6. Инъекции кода с помощью
+конструкции mixin template
+При некоторых программных решениях приходится добавлять шаблон
+ный код (такой как определения данных и методов) в одну или несколь
+ко реализаций классов. К типичным примерам относятся поддержка
+сериализации, шаблон проектирования «Наблюдатель» [27] и передача
+событий в оконных системах.
+Для этих целей можно было бы воспользоваться механизмом наследова
+ния, но поскольку реализуется лишь одиночное наследование, опреде
+лить для заданного класса несколько источников шаблонного кода не
+возможно. Иногда необходим механизм, позволяющий просто вставить
+в класс некоторый готовый код, вместо того чтобы писать его вручную.
+Здесь-то и пригодится конструкция mixin template (шаблон mixin). Стоит
+отметить, что сейчас это средство в основном экспериментальное. Воз
+можно, в будущих версиях языка шаблоны mixin заменит более общий
+инструмент AST-макросов.
+Шаблон mixin определяется почти так же, как параметризированный
+контекст (шаблон), о котором недавно шла речь. Пример шаблона mixin,
+определяющего переменную и функции для ее чтения и записи:
+mixin template InjectX() {
+private int x;
+int getX() { return x; }
+void setX(int y) {
+    ... // Проверки
+x = y;
+}
+}
+Определив шаблон mixin, можно вставить его в нескольких местах:
+// Сделать инъекцию в контексте модуля
+mixin InjectX;
+class A {
+// Сделать инъекцию в класс
+mixin InjectX;
+...
+}
+void fun() {
+// Сделать инъекцию в функцию
+mixin InjectX;
+setX(10);
+assert(getX() == 10);
+}
+Теперь этот код определяет переменную и две обслуживающие ее функ
+ции на уровне модуля, внутри класса A и внутри функции fun – как буд
+то тело InjectX было вставлено вручную. В частности, потомки класса A
+могут переопределять методы getX и setX, как если бы сам класс опреде
+лял их. Копирование и вставка без неприятного дублирования кода –
+вот что такое mixin template.
+Конечно же, следующий логический шаг – подумать о том, что InjectX
+не принимает никаких параметров, но производит впечатление, что мог
+бы, – и действительно может:
+mixin template InjectX(T) {
+private T x;
+T getX() { return x; }
+void setX(T y) {
+... // Проверки
+x = y;
+}
+}
+Теперь при обращении к InjectX нужно передавать аргумент так:
+mixin InjectX!int;
+mixin InjectX!double;
+Но на самом деле такие вставки приводят к двусмысленности: что если
+вы сделаете две рассмотренные подстановки, а затем пожелаете восполь
+зоваться функцией getX? Есть две функции с этим именем, так что про
+блема с двусмысленностью очевидна. Чтобы решить этот вопрос, D по
+зволяет вводить имена для конкретных подстановок в шаблоны mixin:
+mixin InjectX!int MyInt;
+mixin InjectX!double MyDouble;
+Задав такие определения, вы можете недвусмысленно обратиться к внут
+ренним элементам любого из шаблонов mixin, просто указав нужный
+контекст:
+MyInt.setX(5);
+assert(MyInt.getX() == 5);
+MyDouble.setX(5.5);
+assert(MyDouble.getX() == 5.5);
+Таким образом, шаблоны mixin – это почти как копирование и вставка;
+вы можете многократно копировать и вставлять код, а потом указы
+вать, к какой именно вставке хотите обратиться.
+
+7.6.1. Поиск идентификаторов внутри mixin
+Самая большая разница между шаблоном mixin и обычным шаблоном
+(в том виде, как он определен в разделе 7.5), способная вызвать больше
+всего вопросов, – это поиск имен.
+Шаблоны исключительно модульны: код внутри шаблона ищет иденти
+фикаторы в месте определения шаблона. Это положительное качество:
+оно гарантирует, что, проанализировав определение шаблона, вы уже
+ясно представляете его содержимое и осознаете, как он работает.
+Шаблон mixin, напротив, ищет идентификаторы в месте подстановки,
+а это означает, что понять поведение шаблона mixin можно только с уче
+том контекста, в котором вы собираетесь этот шаблон использовать.
+Чтобы проиллюстрировать разницу, рассмотрим следующий пример,
+в котором идентификаторы объявляются как в месте определения, так
+и в месте подстановки:
+import std.stdio;
+string lookMeUp = "Найдено на уровне модуля";
+template TestT() {
+string get() { return lookMeUp; }
+}
+mixin template TestM() {
+string get() { return lookMeUp; }
+}
+void main() {
+string lookMeUp = "Найдено на уровне функции";
+alias TestT!() asTemplate;
+mixin TestM!() asMixin;
+writeln(asTemplate.get());
+writeln(asMixin.get());
+}
+Эта программа выведет на экран:
+Най
+де
+но на уров
+не мо
+ду
+ля
+Най
+де
+но на уров
+не функ
+ции
+Склонность шаблонов mixin привязываться к локальным идентифика
+торам придает им выразительности, но следовать их логике становится
+сложно. Такое поведение делает шаблоны mixin применимыми лишь
+в ограниченном количестве случаев; прежде чем доставать из ящика
+с инструментами эти особенные ножницы, необходимо семь раз отме
+рить.
+
+7.7. Итоги
+Классы позволяют эффективно представить далеко не любую абстрак
+цию. Например, они не подходят для мелкокалиберных объектов, кон
+текстно-зависимых ресурсов и типов значений. Этот пробел восполня
+ют структуры. В частности, благодаря конструкторам и деструкторам
+легко определять типы контекстно-зависимых ресурсов.
+Объединения – низкоуровневое средство, позволяющее хранить разные
+типы данных в одной области памяти с перекрыванием.
+Перечисления – это обычные отдельные значения, определенные поль
+зователем. Перечислению может быть назначен новый тип, что позво
+ляет более точно проверять типы значений, определенных в рамках
+этого типа.
+alias – очень полезное средство, позволяющее привязать один иденти
+фикатор к другому. Нередко псевдоним – единственное средство полу
+чить извне доступ к идентификатору, вычисляемому в рамках вложен
+ной сущности, или к длинному и сложному идентификатору.
+Параметризированные контексты, использующие конструкцию templa
+te, весьма полезны для определения вычислений во время компиля
+ции, таких как интроспекция типов и определение особенностей типов.
+Одноименные шаблоны позволяют предоставлять абстракции в очень
+удобной, инкапсулированной форме.
+Кроме того, предлагаются параметризированные контексты, прини
+мающие форму шаблонов mixin, которые во многом ведут себя подобно
+макросам. В будущем шаблоны mixin может заменить развитое средст
+во AST-макросов.
+
 [^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4).
+[^2]: Термин «клуктура» предложил Бартош Милевски.
+[^3]: Кроме того, ‹код1› может сохранить указатель на значение w, которое исполь
+зует ‹код2›.
+[^4]: На момент написания оригинала книги данная возможность отсутствова
+ла, но поскольку теперь она существует, мы добавили ее описание в пере
+вод. – Прим. науч. ред.

BIN
07-другие-пользовательские-типы/images/image-7-1-1.png


BIN
07-другие-пользовательские-типы/images/image-7-1-11.png