fix links

This commit is contained in:
Alexander Zhirov 2023-02-27 01:07:49 +03:00
parent 0f288152a4
commit 1d0a2964cf
1 changed files with 40 additions and 38 deletions

View File

@ -722,7 +722,7 @@ unittest
Если бы занявший место экземпляра класса `Contact` экземпляр класса `Friend` вел себя *в точности* так же, как и экземпляр ожидаемого класса, отпали бы все (или почти все) причины использовать класс `Friend`. Одно из основных средств, предоставляемых объектной технологией, возможность классам-наследникам переопределять функции классов-предков и таким образом модульно настраивать поведение сущностей среды. Как можно догадаться, переопределение задается с помощью ключевого слова `override` (класс `Friend` переопределяет метод `bgColor`), которое обозначает, что вызов `c.bgColor()` (где вместо c ожидается объект типа `Contact`, но на самом деле используется объект типа `Friend`) всегда инициирует вызов версии метода, предлагаемой классом `Friend`. Друзья всегда остаются друзьями, даже если компилятор думает, что это обыкновенные контакты.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-4-методы-и-наследование) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.1. Терминологический «шведский стол»
@ -736,7 +736,7 @@ unittest
Странно, но несмотря на то что классы это типы, подтип это не то же самое, что и подкласс (а супертип не то же самое, что и суперкласс). Подтип это более широкое понятие: тип `S` является подтипом типа `T`, если значение типа `S` можно без какого-либо риска употреблять во всех контекстах, где ожидается значение типа `T`. Обратите внимание: в этом определении ничего не говорится о наследовании. И на самом деле, наследование это лишь один из способов реализовать порождение подтипов; в общем случае есть и другие средства (в том числе в D). Отношение между порождением подтипов и наследованием можно охарактеризовать так: подтипами класса `C` являются потомки класса `C` плюс сам класс `C`. Подтип `C`, отличный от `C`, это *собственный подтип* (*proper subtype*).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-4-1-терминологический-шведский-стол) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.2. Наследование это порождение подтипа. Статический и динамический типы
@ -790,7 +790,7 @@ unittest
}
```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-4-2-наследование-это-порождение-подтипа-статический-и-динамический-типы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.3. Переопределение только по желанию
@ -802,7 +802,7 @@ unittest
Таким образом, требование писать `override` позволяет модифицировать классы-предки без риска неожиданно навредить классам-потомкам.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-4-3-переопределение-только-по-желанию) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.4. Вызов переопределенных методов
@ -851,7 +851,7 @@ class Derived : Base
Несмотря на то что деструкторы (см. раздел 6.3.4) это всего лишь методы, у обработки вызова деструктора есть особенности. Нельзя явно вызвать деструктор родителя, однако при вызове деструктора текущего класса (или во время цикла сбора мусора, или в результате вызова `clear(obj)`) библиотека D поддержки во время выполнения всегда вызывает все деструкторы вверх по иерархии.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-4-4-вызов-переопределенных-методов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.4.5. Ковариантные возвращаемые типы
@ -902,7 +902,7 @@ void workWith(TextWidget tw)
Чтобы максимизировать количество доступной статической информации о типах, D вводит средство, известное как *ковариантные возвращаемые типы*. Звучит довольно громко, но смысл ковариантности возвращаемых типов довольно прост: если родительский класс возвращает некоторый тип `C`, то переопределенной функции разрешается возвращать не только `C`, но и любого потомка `C`. Благодаря этому средству можно позволить методу `TextWidget.duplicate` возвращать `TextWidget`. Не менее важно, что теперь вы можете прибавить себе веса в дискуссии, вставив при случае фразу «ковариантные возвращаемые типы». (Шутка. Если серьезно, даже не пытайтесь.)
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-4-5-ковариантные-возвращаемые-типы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.5. Инкапсуляция на уровне классов с помощью статических членов
@ -939,7 +939,7 @@ unittest
auto c = (new Widget).defaultBgColor;
```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-5-инкапсуляция-на-уровне-классов-с-помощью-статических-членов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.6. Сдерживание расширяемости с помощью финальных методов
@ -980,7 +980,7 @@ final void updatePrice(double last)
Если вы работали c языками Java или C#, то немедленно узнаете в ключевом слове `final` старого знакомого, поскольку в D оно обладает той же семантикой, что и в этих языках. Если сравнить положение дел с C++, то можно обнаружить любопытную смену настроек по умолчанию: в C++ методы являются финальными по умолчанию (и не нужно специально что-то указывать, чтобы запретить наследование) и переопределяемыми, если явно пометить их ключевым словом `virtual`. Подчеркнем еще раз: по крайней мере в этом случае было решено предпочесть умолчания, ориентированные на гибкость. Скорее всего, вы будете использовать финальные методы в основном для реализации структурных решений и только иногда чтобы избавиться от нескольких дополнительных циклов процессора.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-6-сдерживание-расширяемости-с-помощью-финальных-методов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.6.1. Финальные классы
@ -996,7 +996,7 @@ class PostUltimateWidget : UltimateWidget { ... } // Ошибка! Нельзя
Любопытный побочный эффект финальных классов твердые гарантии реализации. Клиентский код, использующий финальный класс, может быть уверен, что методы этого класса обладают известными реализациями с гарантированным действием, которое не может быть модифицировано никаким подклассом.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-6-1-финальные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.7. Инкапсуляция
@ -1010,7 +1010,7 @@ class PostUltimateWidget : UltimateWidget { ... } // Ошибка! Нельзя
Вернемся к инкапсуляции в контексте языка D. Любые тип, данные, функцию или метод можно определить с одним из следующих пяти спецификаторов. Начнем с самого закрытого спецификатора и постепенно дойдем до полной гласности.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-7-инкапсуляция) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.1. private
@ -1018,13 +1018,13 @@ class PostUltimateWidget : UltimateWidget { ... } // Ошибка! Нельзя
В других языках такой синтаксис имеет другую семантику: обычно доступ к закрытым идентификаторам ограничивают лишь до текущего класса. Тем не менее то, что спецификатор `private` ограничивает доступ до уровня модуля, прекрасно согласуется с общим подходом D к защите: все защищаемые единицы соответствуют защищаемым единицам операционной системы (файлу и каталогу). Преимущество защиты на уровне файла в том, что она способствует объединению маленьких тесно взаимосвязанных сущностей, обладающих определенными обязанностями. Если требуется защита на уровне класса, просто выделите ему собственный файл.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-7-1-private) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.2. package
Спецификатор доступа `package` можно указывать на уровне класса, за пределами классов (на уровне модуля) и внутри структуры (см. главу 7). Во всех контекстах ключевое слово `package` действует одинаково: доступ к идентификатору, определенному с этим ключевым словом, предоставляется всем файлам в том же каталоге, где находится и текущий модуль. Родительский каталог и подкаталоги каталога текущего модуля не обладают никакими особыми привилегиями.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-7-2-package) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.3. protected
@ -1044,7 +1044,7 @@ class C
И снова отметим, что право доступа, предоставляемое спецификатором доступа `protected`, транзитивно: оно переходит не только собственно к дочерним классам, но и ко всем поколениям потомков, наследующих от класса, определенного с ключевым словом `protected`. Это делает спецификатор доступа `protected` довольно щедрым в плане предоставления доступа.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-7-3-protected) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.4. public
@ -1052,13 +1052,13 @@ class C
В языке D `public` это также уровень доступа, который присваивается всем объектам по умолчанию. Поскольку порядок объявлений на компиляцию не влияет, хорошим тоном будет расположить видимые интерфейсы модуля или класса в начале файла, а затем ограничить доступ, применив (например) спецификатор доступа `private`, после чего можно разместить другие определения. Если придерживаться такой стратегии, клиенту будет достаточно посмотреть в начало файла или класса, чтобы узнать все о его доступных сущностях.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-7-4-public) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.5. export
Казалось бы, спецификатор доступа `public` наименее закрытый из уровней доступа, самый щедрый из них. Тем не менее D определяет уровень доступа, разрешающий еще больше: `export`. Идентификатор, определенный с ключевым словом `export`, становится доступным даже *вне* программы, в которой он был определен. Это случай разделяемых библиотек, выставляющих всему миру напоказ свои интерфейсы. Компилятор выполняет зависящие от системы шаги, необходимые для экспорта идентификатора, часто включая и особые соглашения об именовании символов. Пока что в D не определена сложная инфраструктура динамической загрузки, так что спецификатор доступа `export` выполняет роль заглушки в ожидании более обширной поддержки.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-7-5-export) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.7.6. Сколько инкапсуляции?
@ -1084,7 +1084,7 @@ class C
***Рис. 6.3.*** *Приблизительные оценки количества строк кода, которые может затронуть изменение идентификатора с соответствующим спецификатором доступа. Вертикальная ось логарифмическая, так что каждый шаг ослабления инкапсуляции на порядок ухудшает положение дел. Стрелки вверх означают, что количество кода, затронутого при уровне защиты `protected`, `public` и `export`, неподвластно программисту, изменившему идентификатор*
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-7-6-сколько-инкапсуляции) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.8. Основа безраздельной власти
@ -1123,7 +1123,7 @@ class Object
Познакомимся поближе с семантикой каждого из этих идентификаторов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-8-основа-безраздельной-власти) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.1. string toString()
@ -1140,13 +1140,13 @@ unittest
Обратите внимание: вместе с именем класса возвращено и имя модуля, в котором класс был определен. По умолчанию модуль получает имя файла, в котором он расположен, но это умолчание можно изменить с помощью объявления с ключевым словом `module` (см. раздел 11.8).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-8-1-string-tostring) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.2. size_t toHash()
Этот метод возвращает хеш объекта в виде целого числа без знака (размером в 32 разряда на 32-разрядной машине и в 64 разряда на 64-разрядной). По умолчанию хеш-сумма вычисляется на основе поразрядного представления объекта. Хеш является сжатым, но неточным представлением объекта. Одно из важных требований к функции, вычисляющей хеш-сумму, *постоянство*: если метод `toHash` дважды вызывается с одной и той же ссылкой и между двумя вызовами объект, к которому привязана эта ссылка, не был изменен, то значения, возвращенные при первом и втором вызове, должны совпадать. Кроме того, хеш-коды двух одинаковых объектов тоже должны быть одинаковыми. А хеш-коды двух различных («неравных») объектов вряд ли будут равны. В следующем разделе подробно определено понятие равенства объектов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-8-2-size_t-tohash) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.3. bool opEquals(Object rhs)
@ -1272,7 +1272,7 @@ override bool opEquals(Object rhs)
Итак, в общем случае сравнение должно выполняться дважды: каждый из операндов сравнения должен подтвердить свое равенство другому операнду. Но есть и хорошие новости: свободная функция `object.opEquals(Object, Object)` избегает процедуры «рукопожатия» при любом совпадении типов участвующих в сравнении объектов, а в других случаях иногда вообще не инициирует дополнительные вызовы.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-8-3-bool-opequalsobject-rhs) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.4. int opCmp(Object rhs)
@ -1324,7 +1324,7 @@ if (x == y) assert(x <= y && y <= x);
Отношение к `opEquals` определено не слишком строго: вполне вероятна ситуация, когда для двух классов неравенства `x <= y` и `y <= x` истинны одновременно здравый смысл продиктовал бы, что значения `x` и `y` равны. Тем не менее вовсе не обязательно, что `x == y`. Простым примером может послужить класс, определяющий равенство в терминах регистрочувствительных строк, а упорядочивание в терминах строк, нечувствительных к регистру.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-8-4-int-opcmpobject-rhs) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.8.5. static Object factory (string className)
@ -1385,7 +1385,7 @@ void widgetize(string widgetClass)
Теперь функция `widgetize` освобождена от ответственности за выбор конкретного класса из цепочки наследования, которую образует класс `Widget`. Есть и другие пути достижения гибкости конструирования объектов, расширяющие пространство проектирования в разных направлениях. Чтобы познакомиться с всеобъемлющим описанием этой проблемы, внимательно прочтите статью с драматическим названием «Javas new considered harmful» (Оператор new из Java опасен).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-8-5-static-object-factory-string-classname) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.9. Интерфейсы
@ -1452,7 +1452,7 @@ aDayInLife(new CardboardBox, "играть");
Любая реализация интерфейса является подтипом этого интерфейса, так что она автоматически конвертируется в него. Мы воспользовались этим механизмом, просто передав объект `CardboardBox` вместо интерфейса `Transmogrifier`, ожидаемого функцией `aDayInLife`.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-9-интерфейсы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.9.1. Идея невиртуальных интерфейсов (NVI)
@ -1545,7 +1545,7 @@ class Good : Transmogrifier
Этот код корректен, потому конфликт невозможен: вызов будет выглядеть либо как `obj.thereAndBack()` и направится к методу `Transmogrify.thereAndBack`, либо как `obj.thereAndBack(n)` и направится к методу `Good.thereAndBack`. В частности, реализация `Good.thereAndBack` не обязана относить свой внутренний вызов к реализации одноименной функции интерфейса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-9-1-идея-невиртуальных-интерфейсов-nvi) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.9.2. Защищенные примитивы
@ -1599,7 +1599,7 @@ class BrokenInTwoWays : Transmogrifier
Технически осуществимо как ослабление, так и ужесточение требований интерфейса к реализации, но эти возможности вряд ли можно использовать во благо. Интерфейс выражает цель, и должно быть достаточно ознакомиться лишь с определением интерфейса, чтобы полноценно использовать его независимо от доступности статического типа реализуемого класса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-9-2-защищенные-примитивы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.9.3. Избирательная реализация
@ -1628,7 +1628,7 @@ class TimedApp : Timer, Application
Чтобы получить доступ к этим методам объекта `app` типа `TimedApp`, вам придется написать `app.Timer.run()` и `app.Application.run()` для версий интерфейсов `Timer` и `Application` соответственно. Класс `TimedApp` может определить собственные функции, которые будут делегировать вызов этим методам. Главное, чтобы такие функции не пытались подменить `run()`.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-9-3-избирательная-реализация) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.10. Абстрактные классы
@ -1792,7 +1792,7 @@ unittest
}
```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-10-абстрактные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.11. Вложенные классы
@ -1882,7 +1882,7 @@ class Outer
}
```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-11-вложенные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.11.1. Вложенные классы в функциях
@ -1947,7 +1947,7 @@ Calculation truncate(double limit)
Но благодаря небольшой поддержке компилятора рассмотренный пример работает нормально. Когда бы компилятору ни приходилось компилировать функцию, он всегда просматривает ее в поисках нелокальных утечек (non-local escapes) ситуаций, когда параметр или локальная переменная остается в использовании уже после того, как функция вернула результат[^12]. Если обнаружена такая утечка, компилятор изменяет способ выделения памяти под локальное состояние (параметры плюс локальные переменные): вместо выделения памяти в стеке в этом случае происходит динамическое выделение памяти.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-11-1-вложенные-классы-в-функциях) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.11.2. Статические вложенные классы
@ -1976,7 +1976,7 @@ unittest
Будучи обычным классом, статический внутренний класс не имеет доступа к внешнему объекту просто за отсутствием таковых. Зато благодаря контексту определения статический внутренний класс обладает доступом к статическим внутренним элементам внешнего класса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-11-2-статические-вложенные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.11.3. Анонимные классы
@ -2002,7 +2002,7 @@ Widget makeWidget(uint w, uint h)
Это средство языка работает почти так же, как анонимные функции. Создание анонимного класса эквивалентно созданию нового именованного класса с последующим созданием его экземпляра. Эти два шага сливаются в один. Такое малопонятное средство может показаться бесполезным, но на практике многие проектные решения широко его используют для связи наблюдателей и субъектов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-11-3-анонимные-классы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.12. Множественное наследование
@ -2109,6 +2109,8 @@ class Sprite3 : HyperObservantActor, VisualActor
О достоинствах и недостатках множественного наследования спорят давно. Дебаты продолжаются и вряд ли прекратятся в ближайшем будущем, но в одном оппоненты сошлись: реализовать множественное наследование так, чтобы оно одновременно было простым, эффективным и полезным, непросто. Одни языки, придавая меньшее значение эффективности, делают выбор в пользу дополнительной выразительности, которую привносит множественное наследование. Другие языки, заложив основу высокой производительности (например, с помощью непрерывных объектов и скоростной диспетчеризации функций), ради этого ограничивают гибкость возможных программных решений. Интересное решение, позволяющее задействовать большинство преимуществ множественного наследования без свойственных ему проблем, примеси в языке Scala (по сути, это интерфейсы, укомплектованные реализациями по умолчанию). Подход языка D заключается в разрешении множественного *порождения подтипов*, то есть порождения подтипов без наследования. Посмотрим, как это работает.
[В начало ⮍](#6-12-множественное-наследование) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.13. Множественное порождение подтипов
Продолжим достраивать программу, использующую класс `Shape`. Допустим, требуется определить объекты класса `Shape`, которые можно было бы хранить в базе данных. Мы нашли отличную библиотеку, которая обеспечивает постоянство объектов с помощью базы данных, что прекрасно нам подходит, кроме одного: она требует, чтобы каждый сохраняемый объект наследовал от класса `DBObject`.
@ -2160,7 +2162,7 @@ unittest
В классе может быть неограниченное число объявлений `alias this`, таким образом, класс может стать подтипом неограниченного числа типов[^14].
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-13-множественное-порождение-подтипов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.13.1. Переопределение методов в сценариях множественного порождения подтипов
@ -2242,7 +2244,7 @@ class StorableShape : Shape
}
```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-13-1-переопределение-методов-в-сценариях-множественного-порождения-подтипов) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.14. Параметризированные классы и интерфейсы
@ -2323,7 +2325,7 @@ unittest
Как только вы создадите экземпляр параметризированного класса, он превратится в обычный класс, так что `StackImpl!int` это такой же класс, как и любой другой. Именно этот конкретный класс реализует `Stack!int`, поскольку в формочку для вырезания `StackImpl(T)` под видом `T` вставили `int` по всему телу этого класса.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-14-параметризированные-классы-и-интерфейсы) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
### 6.14.1. И снова гетерогенная трансляция
@ -2362,7 +2364,7 @@ class StackImpl(T, Backend) : Stack!T
}
```
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-14-1-и-снова-гетерогенная-трансляция) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.15. Переопределение аллокаторов и деаллокаторов[^16]
@ -2468,7 +2470,7 @@ unittest
К тому же, как всегда, при большой силе большая ответственность: если вы будете выделять память под классы, не используя кучу под контролем сборщика мусора, и ваш класс содержит указатели (в том числе неявные, унаследованные от классов-родителей) на данные, выделенные в памяти сборщика мусора, вы обязаны декларировать их создание сборщику мусора вызовом функции `core.memory.GC.addRange()`. Иначе сборщик мусора не будет знать о ссылках на данные, хранящихся в ваших классах, и может счесть мусором данные, на которые ваши классы все еще ссылаются. Это может повлечь за собой порчу данных и трудноуловимые ошибки программы, так как в случаях порчи данных сбой может произойти уже спустя пару минут после порчи в совершенно постороннем, невинном коде, который всего лишь пытался использовать свою ненароком испорченную область памяти. Мы вас предупредили!
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-15-переопределение-аллокаторов-и-деаллокаторов-16) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.16. Объекты scope[^17]
@ -2596,7 +2598,7 @@ unittest
Конструкция `scoped!T`, которую можно найти в модуле `std.typecons`, столь же небезопасна, но не засоряет очередной излишней возможностью язык (осложняя этим жизнь авторам компиляторов D).
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-16-объекты-scope-17) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
## 6.17. Итоги
@ -2612,7 +2614,7 @@ unittest
D полностью поддерживает технику невиртуальных интерфейсов, а также полуавтоматический механизм для множественного порождения подтипов.
[В начало ⮍]() [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[В начало ⮍](#6-17-итоги) [Наверх ⮍](#6-классы-объектно-ориентированный-стиль)
[^1]: Язык D также предоставляет возможность «ручного» управления памятью (manual memory management) и на данный момент позволяет принудительно уничтожать объекты с помощью оператора delete: `delete obj;`, при этом значение ссылки `obj` будет установлено в `null` (см. ниже), а память, выделенная под объект, будет освобождена. Если `obj` уже содержит `null`, ничего не произойдет. Однако следует соблюдать осторожность: повторное уничтожение одного объекта или обращение к удаленному объекту по другой ссылке приведет к катастрофическим последствиям (сбои и порча данных в памяти, источники которых порой очень трудно обнаружить), и эта опасность усугубляет необходимость в сборщике мусора. Из-за этих рисков оператор `delete` планируют убрать из самого языка, оставив в виде функции в стандартной библиотеке. Но при этом ручное управление памятью позволяет более эффективно ее использовать. Вердикт: задействуйте эту возможность, если уверены, что на момент вызова `delete` объект `obj` точно не удален и `obj` последняя ссылка на данный объект, и не удивляйтесь, если в один прекрасный день `delete` исчезнет из реализаций языка. *Прим. науч. ред.*
[^2]: На данный момент реализации D предоставляют средства выделения памяти под классы в стеке (с помощью класса памяти `scope`) или вообще в любом фрагменте памяти (с помощью классовых аллокаторов и деаллокаторов). Но поскольку эти возможности небезопасны, они могут быть удалены из языка, так что не рассчитывайте на их вечное существование. *Прим. науч. ред.*