dlang-book/07-другие-пользовательские-...
Alexander Zhirov db7921a6b9 7 fix 2023-02-27 01:39:39 +03:00
..
README.md 7 fix 2023-02-27 01:39:39 +03:00

README.md

7. Другие пользовательские типы

  • 7.1. Структуры
    • 7.1.1. Семантика копирования
    • 7.1.2. Передача объекта-структуры в функцию
    • 7.1.3. Жизненный цикл объекта-структуры
      • 7.1.3.1. Конструкторы
      • 7.1.3.2. Делегирование конструкторов
      • 7.1.3.3. Алгоритм построения
      • 7.1.3.4. Конструктор копирования this(this)
      • 7.1.3.5. Аргументы в пользу this(this)
      • 7.1.3.6. Уничтожение объекта и освобождение памяти
      • 7.1.3.7. Алгоритм уничтожения структуры
    • 7.1.4. Статические конструкторы и деструкторы
    • 7.1.5. Методы
      • 7.1.5.1. Оператор присваивания
      • 7.1.5.2. Сравнение структур на равенство
    • 7.1.6. Статические внутренние элементы
    • 7.1.7. Спецификаторы доступа
    • 7.1.8. Вложенность структур и классов
    • 7.1.9. Структуры, вложенные в функции
    • 7.1.10. Порождение подтипов в случае структур. Атрибут @disable
    • 7.1.11. Взаимное расположение полей. Выравнивание
      • 7.1.11.1. Атрибут align
  • 7.2. Объединение
  • 7.3. Перечисляемые значения

Применяя классы, основные типы и функции, можно написать много хороших программ. С параметризированными классами и функциями дело идет еще лучше. Но нередко мы с сожалением отмечаем, что по нескольким причинам классы не представляют собой инструмент с максимальной абстракцией типа.

Во-первых, классы подчиняются ссылочной семантике и из-за этого могут воплощать многие проектные решения не полностью или с ощутимыми накладными расходами. На практике трудно моделировать с помощью класса такую простую сущность, как точка с двумя или тремя координатами, если таких точек больше нескольких миллионов: разработчик оказывается перед непростым выбором хорошая абстракция или приемлемое быстродействие. Кроме того, для линейной алгебры ссылочная семантика большая морока. Попробуйте убедить математика или программиста-теоретика, что присваивание a = b должно делать из матрицы a лишь псевдоним матрицы b, а не отдельную копию! Даже такой простой тип, как массив, довольно накладно моделировать в виде класса в сравнении с мощной и лаконичной абстракцией массива, имеющейся в языке D (см. главу 4). Можно, конечно, сделать массивы «волшебными», но опыт то и дело показывает, что предоставлять множество «волшебных» типов, не воспроизводимых в пользовательском коде, дурной тон и признак плохо спроектированного языка. Затраты на массив всего два слова, а выделение памяти под экземпляр класса и использование дополнительного косвенного обращения означают большие накладные расходы по памяти и времени для всех примитивов массива. Даже такой простой тип, как int, нельзя выразить в виде класса дешево и элегантно (причем речь не об удобстве оператора). У такого класса, как BigInt, та же проблема: a = b делает нечто совершенно иное, чем соответствующая операция присваивания для типа int.

Во-вторых, классы живут вечно, а значит, с их помощью трудно моделировать ресурсы с выраженным конечным временем жизни (такие как дескрипторы файлов, дескрипторы графического контекста, мьютексы, сокеты и т. д.). Работая с такими ресурсами как с классами, нужно постоянно быть начеку, чтобы не забыть своевременно освободить инкапсулированные ресурсы с помощью метода, вроде close или dispose. В таких случаях обычно помогает инструкция scope (см. раздел 3.13), но лучше, когда подобная контекстная семантика инкапсулирована в типе раз и навсегда.

В-третьих, классы это механизм для довольно «тяжелых» и высокоуровневых абстракций, то есть они не позволяют легко выражать «легковесные» абстракции вроде перечисляемых типов или псевдонимов для заданного типа.

D не был бы настоящим языком для системного программирования, если бы предоставлял для выражения абстракций только классы. Кроме классов в запасе у D есть структуры (типы-значения, сравнимые с классами по мощности, но с семантикой значения и без полиморфизма), типы enum (легковесные перечисляемые типы и простые константы), объединения (низкоуровневое хранилище с перекрыванием для разных типов) и вспомогательные механизмы определения типов, такие как alias. Все эти средства последовательно рассматриваются в этой главе.

В начало ⮍

7.1. Структуры

Структуры позволяют определять простые, инкапсулированные типы-значения. Удобная аналогия тип int: значение типа int это 4 байта, допускающие определенные операции. В int нет никакого скрытого состояния и никаких косвенных обращений, и две переменные типа int всегда ссылаются на разные значения1. Соглашение о структурах исключает динамический полиморфизм, переопределение методов, наследование и бесконечное время жизни. Структура это преувеличенный тип int.

Как вы помните, класс ведет себя как ссылка (см. раздел 6.2), то есть вы всегда манипулируете объектом посредством ссылки на него, причем копирование ссылок лишь увеличивает количество ссылок на тот же объект без дублирования самого объекта. А структура это тип-значение, то есть, по сути, ведет себя «как int»: имя жестко привязано к представленному им значению, а при копировании значения структуры на самом деле копируется целый объект, а не только ссылка.

Определяют структуру так же, как класс, за исключением следующих моментов:

  • вместо ключевого слова class используется ключевое слово struct;
  • свойственное классам наследование и реализация интерфейсов запрещены, то есть в определении структуры нельзя указать :BaseType или :Interface, и очевидно, что внутри структуры не определена ссылка super;
  • методы структуры нельзя переопределять все методы являются финальными (вы можете указать в определении метода структуры ключевое слово final, но это было бы совершенно излишне);
  • нельзя применять к структуре инструкцию synchronized (см. главу 13);
  • структуре запрещается определять конструктор по умолчанию this() (см. раздел 7.1.3.1);
  • структуре разрешается определять конструктор копирования (postblit constructor) this(this) (см. раздел 7.1.3.4);
  • запрещен спецификатор доступа protected (иначе предполагалось бы наличие структур-потомков).

Определим простую структуру:

struct Widget
{
    // Константа
    enum fudgeFactor = 0.2;
    // Разделяемое неизменяемое значение
    static immutable defaultName = "Виджет";
    // Некоторое состояние, память под которое выделяется для каждого экземпляра класса Widget
    string name = defaultName;
    uint width, height;
    // Статический метод
    static double howFudgy()
    {
        return fudgeFactor;
    }
    // Метод
    void changeName(string another)
    {
        name = another;
    }
}

В начало ⮍ Наверх ⮍


  1. Не считая эквивалентных имен, создаваемых с помощью alias, о чем мы еще поговорим в этой главе (см. раздел 7.4). ↩︎