From 0e79ca3ab4b80a7694f8a070092f2ea1e506239e Mon Sep 17 00:00:00 2001 From: Alexander Zhirov Date: Mon, 27 Feb 2023 18:03:33 +0300 Subject: [PATCH] 7.3.1 --- 07-другие-пользовательские-типы/README.md | 2464 +++++++++++++++++ .../images/image-7-1-1.png | Bin 0 -> 15569 bytes .../images/image-7-1-11.png | Bin 0 -> 20615 bytes 3 files changed, 2464 insertions(+) create mode 100644 07-другие-пользовательские-типы/images/image-7-1-1.png create mode 100644 07-другие-пользовательские-типы/images/image-7-1-11.png diff --git a/07-другие-пользовательские-типы/README.md b/07-другие-пользовательские-типы/README.md index de37048..1f2b1ca 100644 --- a/07-другие-пользовательские-типы/README.md +++ b/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]: На момент написания оригинала книги данная возможность отсутствова +ла, но поскольку теперь она существует, мы добавили ее описание в пере +вод. – Прим. науч. ред. diff --git a/07-другие-пользовательские-типы/images/image-7-1-1.png b/07-другие-пользовательские-типы/images/image-7-1-1.png new file mode 100644 index 0000000000000000000000000000000000000000..2ad223273d16e338c803de987e6c63eedc28eac1 GIT binary patch literal 15569 zcmd6ObyQVd^zHo=MM6PT5G19fr5i+~yYteW($YwWba!`m*M|s5bLnop)TKM##_u=Y z7;lXC?;GPC(F=0VIs5Lj*IIMUId_n(w8(Q*JX8n-@?1>xi#!DKmQrs|m3*e8Cz1!cLszXu0XKk7bu7<7d7jqjmIYfC{{ z7Vp)`A#A`DHl*EDFj-AGQClN26eb~x|NQv>{8Yd9(c_rJVa#I^!66b75^A(@2?+@? zHTF|wP;zqeLTVH+wv59hRw%jW<Myd*L7WtVPyK zZG∨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 U=Ud!M!I&UoLegK#KI?e@F9ZQ@oB#j- literal 0 HcmV?d00001 diff --git a/07-другие-пользовательские-типы/images/image-7-1-11.png b/07-другие-пользовательские-типы/images/image-7-1-11.png new file mode 100644 index 0000000000000000000000000000000000000000..275f0199b8ae102b930b364dccc41830bf86fc37 GIT binary patch literal 20615 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