И.Г. Головин - Конспект лекций по курсу Языки программирования (1161120), страница 21
Текст из файла (страница 21)
Нужно вызыватьконструктор сразу, как только объект разместился. Проблема в обеспечении вызова этойфункции. Ни один класс адекватно не решает эту проблему.Классификация конструкторов (С++):1. Умолчания X() – когда программе не указано явно, какой конструктор вызывать2. Копирования X(const X1) – инициализация путем копирования3. Преобразования X(T) – определяет преобразование из X в T4. Остальныеoperator имяТипа () – находится внутри класса X.Определяет преобразование из Т в Х, т.е. обратное. Когда он нам нужен? Почему нельзя дляT создать соответствующий конструктор? Как написать конструктор для класса, если онклассом не является? Или вдруг нам по каким то причинам вдруг нужно преобразовывать избазового в производный тип. Вообще говоря, поддержка неявных преобразований – вещьнеприятная.Что такое сильная и слабая типизация?Сильная – избегнуть нельзя (например, АДА – нет преобразований между типами).Слабая – С и С++ (т.к.
сознательно допускаются неявные преобразования).В начале 80-х была мода на сильную типизациюINTEGERCARDINALВ С с классами не было никаких явных преобразований. Новые ТД становилисьнеотличимыми от старых. Страуструп добавил только ссылочный тип и надежныйввод/вывод.Рассмотрим выражение с арифметическими операциями */+a = f(x) * D* k* exp(-i*j)/B, где f – вещественное, k – комплексноеКак записать использую только префиксную форму?*(F(x),*(D,*(k,*(/(exp(-i,j),B))))Придется написать еще кучу функций для разных наборов аргументов.
Получается слишкоммного операций, либо явные преобразования. А хотелось бы работать как со встроеннымичисловыми типами. Если у нас есть 10 типов и 6 операций, то понадобится около 120функций! Причем они будут тривиальными.Есть языки, в которых нет тривиальных преобразований: Java, Delphi, классическая Ада,Модула-2 и так далее.
Там нет переопределения стандартных операций.128В C# неявные преобразования допустимы только между классами (из одного класса вдругой). Преобразователь обязан быть статической функцией.static operator X(X a, Y b) {…} – определяет только явное преобразование.Y y;(X) y; – явное преобразованиеC++: проблема неявного вызова оператора преобразования.class Vector {T* body;int size;public:explicit Vector(int size); – оператор явного преобразования~Vector();T& operator [](int i);};Vector V(128);v = 0; // если не писать explicit, то компилятор не выдаст ошибкуВ C# есть два ключевых слова – explicit и implicit.
В C++ следовало по умолчанию выбратьimplicit, но переделывать не стали из-за слишком большого объема уже написанного кода. ВC# нельзя переопределить = , так как копируются ссылки, а не объекты. [], () тоже нельзяпереопределять, что неудобно. Для обхода этого «прибили» индексатор.class WithIndexer {T this [int index] {get {…}set { value: …}}}WithIndexer c;c[0] = 0; // вызывается seta = c[1]; // вызывается getТипы индекса могут быть самыми разными:В языках со ссылочной семантикой ситуация проще. Таких языков значительно меньше.Проблемы копирования решаются с помощью других средств.a = b – вообще говоря, присваивание ссылокX a(b) ~ X a = b ~ a = b (в ссылочных языках)Остается конструктор умолчания – единственный конструктор, который вызываетсянеявно, все остальные вызываются явным образом.X (…): … {…}129Системная часть (генерируемая) – код, который автоматически генерируетсякомпилятором.Пользовательская часть – {…}.Все подобъекты в ссылочных языках – ссылки.
Допустимы инициализаторы такого вида:сlass X {Y a = new Y();// такой инициализации чаще всего хватаетX(){…}};Вначале вызываются конструкторы подобъектов (автоматически). Существует только 1случай, когда нужно вызывать явно конструктор – вызов конструкторов базовых классов.Любой конструктор начинается с того, что вызывает конструкторы базовых классов, тотсвоего базового, и т.д. – Object.В Java:X(…){super(0);…} //терминология SmallTalkX(…){super();…} // такой код генерируется автоматическиX(…): base(0)this(0,0)//синтаксический сахар в стиле C++Все остальные конструкторы – явные: new X(…)За счет ссылочной семантики сами языки значительно упрощаются.Копирование: универсального способа решения нет.Уничтожение. Особняком стоит Delphi:type X = class constructor Create;destructor Destroy;end;Названия такие, потому что такие же названия в классе TObject, мы просто переопределяем.X a;a = X.Create;Возникает очень интересная вещь в Delphi.
Все конструкторы вызываются только явнымобразом. Компилятор языка Delphi никогда не генерирует автоматически вызовконструктора.constructor X.Create;begin130inherited Create;//вызывается соответствующая функция из базовогокласса…end;В других языках используется синтаксис C++: X(), ~X(). Автоматически генерируетсяконструктор умолчания, если нет другого конструктора.Дополнительные конструктор/деструктор:LoadSaveДля сериализации объектов (сохранения объектов во внешней памяти).static int i = 0;//С++ запрещаетЧто такое виртуальные функции? Это функции, вызов которых динамически связывается стелом.X.f();X->f();//«Дайте мне X и я переверну Землю.»Только через указатель или ссылку можно вызвать виртуальную функцию.Во всех языках со ссылочной семантикой существует понятие метакласс – класс, которыйработает с произвольными классами. Метаклассы ипользуются для цели рефлексии.
С еепомощью можно получить имя типа. Рефлексия – развитие идеи динамическойидентификации типа.Программными средствами языка можно реализовать динамическую подгрузку модулей. Вотличие от динамически загружаемых библиотек, проверяется корректность сигнатур.constructor Create; virtual;//описание в классе TObjectЗаметим, что в других языках, в которых конструкторы не бывают виртуальными,конструктор – это некая виртуальная функция, которая генерирует объекты определенноготипа.virtual X*MakeX(); //виртуальный конструктор в языке Delphi~X(); - в C# зачем-то ввели такую штуку, на самом деле она сбивает с толкуВ языке C++ все просто, но за это у нас нет автоматической сборки мусора.Если язык имеет ссылочную семантику, то как отловить момент, когда удалять объект?В Delphi есть метод Free, он вызывает деструктор объекта, а затем освобождает память.a.Free;131a:=nil;RAII: Resource Acquisition Is Initialization: захват ресурса есть инициализация.RAII => X(); - захват, ~X(); - освобождениеRAII методика удобна только для коротко живущих объектов.Пример (C# или Java)try {}finally {}try<операторы>finally<операторы>endФактически, с помощью finally части мы можем сами вызывать свертку стека.В Java:protected void finalize(); – автоматически вызывается тогда, когда объектперестает существоватьВ библиотеке .Net есть класс Image.
У него есть шикарный статический методImage.FnewFile(“f.jpg”);Чем плохо? Есть блокировка файлов, например, пока мы его считываем, то блокируем отзаписи. Пока мы работаем с этим образом, он не будет изменен. Блокируется в деструкторе,а тот будет когда вызван? Никогда. Ну естественно когда-то то будет вызван, нопользователю то об этом не скажешь.Интересно, что как только в языке появляется сборка мусора, появляется метод дляудаления объектов.protected void finalize() {Close();Dispose();…}Динамическая сборка мусора помогает только в очень простых программах. Часто вызываетнепредвиденные накладные расходы.Надежные отказоустойчивые сервера пишут только на C/C++.132ILDASM – программа, предоставленная Microsoft.Любой компилятор .Net генерирует промежуточный код, а дизассемблировать его можно спомощью этой утилиты.
Если мы дизассемблируем деструктор, то вызывается приватныйметод класса Object – finalize().FileStream fs = new FileStream();try {}finally {fs.Close();}IDisposible:void Dispose();FileStream fs = new FileStream();try {}finally {((IDisposible)fs).Dispose();}Большинство программ имеют соответствующую структуру.Using (v = expr){блок}v = expr;try {}finally {((IDisposible)v).Dispose();}В языке C++ этого нет, нужно пользоваться методикой RAII.Во всех остальных языках она есть, фактически является заменителем свертки стека.X a = b;a = b;Copy(); shallow copy – поверхностное копирование (для плоских объектов)Clone(); deep copy133topsizesizesizeJava:protected Object clone();C#: protected ObjectMemberwise Clone();NB: ICloneable – безграмотно с точки зрения английского языка, правильно было быIClonableICloneableObject Clone();Нельзя сказать глубокая или неглубокая была копия.