Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Свитки
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  23:42[Войти] | [Зарегистрироваться]

ЯП, ОПП и т.д. и т.п. в свете безопасности программирования. IV

Дмитрий Логинов
дата публикации 16-10-2000 14:02

ЯП, ОПП и т.д. и т.п. в свете безопасности программирования. IV

Проследуем дальше. 1969 год наконец-то завершается долгострой от AT&T Lab. Проект MULTICS, целью которого было создание ОС для микрокомпьютеров DEC, и не просто ОС, а многопользовательской и многозадачной ОС, с новой файловой системой, обеспечивающей хорошую безопасность при использовании в сетях. И вот спустя почти 9 лет программисты Кеннет Томпсон и Деннис Ричи выпускают ОС под названием UNIX. Да, всезнающий читатель скажет: "Лоханулся ты мужик Юникс вышла раньше". Да, внимательные вы мои, вы правы. По мере продвижения проекта MULTICS в 1963 году выходит сначала CTSS(Compatible Time-Sharing System), затем на "пидипишном" ассемблере выходит UNICS, перед этим были пробные выпуски MULTIC OS на ПЛ-1. Никакого OpenSource, никаких перекомпиляций. Еще нет Инета, еще нет Си. Первая локалка-то появилась всего 5 лет назад, под чутким оком DARPA. Инет токо зарождался, а его главный эксплуататор, БГ, с другом Полом создали небольшую конторку по отладке прог для PDP-8. Нет, эта конторка называлась пока не MicroSoft.

Из того, что появилось в 70-ые отмечу появление SmallTalk, славного и более удачного продолжения SIMUL-ы, и появление С. Вам, безусловно, либо известна история его появления, либо глубоко неинтересна. Поэтому остановлюсь только на одном моменте. То ли из-за лени, то ли из-за соревнования с авторами Би, но Деннис Ричи все-таки придумал этот язык("И дьявол стоял у него за спиной!" - подумает пасквилянт.). Как я уже говорил, коллега Денниса, Кен Томпсон, налабал на асме операционку. Нехилую операционку. Не могу сказать, что "пидипишный" асм сложен. Но на асме программировать большие проекты БОЛЬНО. А уж операционки масштаба Юникс - это зверский труд. Может, я думаю, Ричи хотел язык высокого уровня с возможностями асма. И у него получилось. Конечно, часто программы на Сях рожают код несколько больший, чем если бы его написал ассемблерный программер. Но баланс был достигнут. Доказательство тому - переписанное ядро Юникс, но уже на Сях. Т.к. программерам в "лаборатории колокольчиков" нужна была некая замена асма, то АЛГОЛОподобный язык отпадал сразу. Поэтому был придуман несколько иной синтаксис. Нельзя сказать, что он более читабелен, но и нельзя сказать, что он нечитабелен вовсе. Когда я с Паскаля первый раз переходил на Си(это было до С++), то 80% процентов текста программ-примеров я понимал. Остальные 20% упирались в незнание библиотечных функций и стандартных операторов языка. Си полностью справился со своей задачей. Системное программирование тогда было в фаворе. Если уж в тяжелых на подъем Советских Вузах появилась соответствующая специальность, то можно только догадываться о том какой спрос был на системщиков на Западе. Для себя я это объясняю лишь отсутствием ПРЕОБЛАДАЮЩЕЙ архитектуры компьютеров, не было преобладающей ОС. Даже столь известная СР/М с ее 23 командами появится лишь в 1976 году. Как следствие, наличествует огромное кол-во библиотек, интерфейсов и прочая и прочая. Чаще приходилось это все писать самому "заново", под заказчика. Вот тут и выходили на первый план системщики.

На примере появления Си, можно увидеть, что безопасность разработки была успешно поставлена в угоду более эффективному решению задачи. Сейчас, когда ОС пишут на большом количестве языков, в том числе и на Виртовских, найдется уйма народу, которые возразят моей мысли, сформулированной в предыдущем предложении. Я не собираюсь говорить о том, что они или вы не правы. Зачем мне это надо? Просто Си это сделал первым из языков высокого уровня - это раз. Два - все зависит от количества кода спрятанного во вспомогательных библиотеках. Завершая абзац, я не собираюсь отстаивать право какого-нибудь языка на первое место среди всех языков. Я, программирующий на С++, не знаю чем объяснить большое кол-во программистов на Си. Тогда не было МС, не было преобладающих ОС или API. Но количество СИшников росло с каждым днем. Я не хочу сказать, что ВИРТовцев держали в темном теле до появления БОРЛАНД, нет! Здесь не уместны аналогии, но нам в техническом ВУЗЕ не преподавали СИ. Мы сидели за Паскалем, Модулой-2, Фортраном, ПЛ-1 и за ассемблерами. Ну, конечно, желающие могли на старших курсах писать курсачи на Сях. Вот и пойми кого держали в темном теле. Да, на СИ, но не на С++, программировать учить трудней, чем на Паскале. Ну и что? Учитесь на здоровье, а что дальше? Для нормального роста надо переходить от простого к сложному. Ну ладно, что-то одни эмоции поперли.

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

Итак, мы остановились на 1972 году. К тому времени уже известен закон Мура, а Интел выпустила свой первый "камень" - Intel 4004. Ну а в 1972 году Нолан Башнелл, автор игры Pong(прародитель Арканоида), основывает компанию ATARI video games. В этом же году выходит первый программируемый инженерный калькулятор от Hewlett-Packard, называется HP-35. Это вершина "калькуляторной" гонки вооружений, начатой год назад выпуском Poketronic. В этом же году DARPA выпускает протокол связи TelNet. Сейчас этот порт почти все админы закрывают, но тогда это было первым шагом к Инету. Сеймур Крей основывает отдельную компанию, которую называет своим именем.

1973 год такой же жаркий как и его предшественник. В Европе появляется французский коммерческий ПК Micral. На нем установлено новое детище Интела - процессор Intel 8008. А ТЕПЕРЬ ВНИМАНИЕ! Всю математику к нему разрабатывал и писал Филипп Канн. Этот ПК продавался французской кампанией R2E целых 6 лет, по цене всего 1750 баксов. А вообще этот год стал ГОДОМ XEROX. Ее лаборатория XEROX PARC(Palo Alto Research Center), выпускает лазерный принтер, а чтобы дать возможность пользоваться этим дорогим удовольствием всем, придумывает в добавок Ethernet. Но это не останавливает лабораторию. Она не стоит на месте. Помните я уже говорил, что в годом раньше из этой чудной лаборатории выходит SmallTalk. Разрабатывали его не просто, как подвижку Симулы. Он создавался для написания ОС для своего ПК. Представляете! 1973 год на дворе, а народ на ООП языке пишет ОС с оконным интерфейсом, иконками и поддержкой мыши! И за всей этой шумихой проходит незамеченным стандартизация DARPA протокола передачи файлов - FTP.

Лишь спустя год, в 1974 году, после первого европейского ПК(Micral), США выпускает свой "первый" коммерческий ПК SCELBI-8H, и тоже на базе Intel 8008, но по цене 500 баков. Теперь становится понятным почему Micral не продавался в США. А в это время в DARPA полным ходом едет обкатка TCP.

ВНИМАНИЕ, поклонники МС, все к экрану. 1975 год, выходит ALTAIR 8800 на базе новейшего шедевра от Интел, процессора Intel 8080. Разработал это дело, производил и продавал Эдд Робертс из MIT(см.выше). "А рядом прикорнула зубастая акула". В смысле Гейтс с Алленом лицензируют BASIC, как язык программирования для Альтаира и Эдд Робертс, его автор, заказывает им впихнуть его в 256 байт Альтаира. Как вы думаете чем дело закончилось? Да, - БГ пришел к Эдду и сказал: "Парень нам надо 4Кило памяти!". Робертс кидается разрабатывать дополнительную плату расширения для своей же альтаировской шины S-100. Но, как и все сделанное наспех, плата эта работает не стабильно. Самодельщики начинают "пиратствовать", сами делают свои платы. Так БГ открыл эру "компьютерного пиратства" и "завалил" Альтаир. Он продавался только 1.5 года, даже не смотря на смешную цену в 297 баксов. В этом же году выходит visual display module (VDM) родитель всех цифровых дисплеев. Автор этого чуда, Lee Felsenstein, через год пропихивает это дело в Альтаир. Тогда же, в 1975, на Tandem-16 демонстрируется принцип OLTP.

1976 год, два Стива, Стив Возняк и Стив Джобс собирают APPLE-I и за год успевают продать его 200 штук, по цене в 500 долларов. Есть данные, что цена была 666.66 долларов. Все может быть. В том же году выходит CRAY-I. Опять МС, но косвенно. В 1976 году Гарри Килдэлл представляет ОС для 8ми разрядных камней, под названием CP/M, написанную на переделке ПЛ-1.

А вот и 1977 год - выходит первый ПК на печатных платах, да еще с цветным дисплеем, да еще с флопом, да еще с 4К памяти, да еще с игрой "Breakout", да еще по цене 1298$. Имя ему APPLE-II. А на ПК Commodore PET впервые демонстрируется мембранная клавиатура. Мало того, впервые на ней вынесены клавиши управления курсором, в том виде в каком мы привыкли это видеть. Также появляется прямой конкурент APPLE-II, ПК под названием TRS-80(Tandy Radio Shack). А циферка "80" происходит от оооочень интересного камня, на котором построен этот комп, это Z80. Встроенный ВАСИК, 4К памяти, понимает кассеты и продался числом в 10000 штук по цене 599.95$ за каждого. В этом же году выходит DES.

1978 год выходит VAX 11 от DEC.
1979 год - Bob Frankston и Dan Brinklin из Гарварда выпускают ПЕРВУЮ электронную таблицу VisiCalc.
Уфф! Все заканчиваю с историей.

1980 год. Разработан принцип DRAM и выпускается его 64К модуль. Выходит 3 32-битных камня Motorola 68000(разрабатывался для Lisa), NS16000(National Semiconductor непонятно для кого разрабатывал) и Intel 80186(разрабатывался для IBM). Родитель CP/M отказывается дать свою ОС IBM для ее первого ПК и первого ПК с открытой архитектурой. Гарри и не знал кому он вымащивает дорожку к "золоту". Тут как тут - проныра Билли, начинающий ворюга и пират, перелопачивает CP/M и заключает контракт с IBM.

Та-таааа! 1981 год выходит IBM PC на Intel 8088 это 4.77 МГц, с 16К памяти, с FDD и с MS-DOS. Все это стоило 1600$. Огромный успех! Дальше вы знаете, TCP/IP, PostScript, AutoDesk, Lotus, Dbase, Windows, Word, Digger м прочие. Нас интересует 1985 год. Нет не перестройка - появление С++.

Вот и закончился экскурс по эволюции языков - мы дошли до С++ и его производных. Надо подвести очередной маленький промежуточный итог. В программировании появляются "большие" проекты. Теперь сфера задач, которые должен решать программист расширилась неимоверно. Середина 80ых доказала преимущества открытой архитектуры. Одомашнивание ПК раздвинуло рынок до немыслимых масштабов. Началась война за звание "лучшей" ОС для самого распространенного класса ПК. APPLE зашел в тупик и его нельзя было назвать конкурентом IBM-совместимой архитектуры. Нет МАКи не погибли, просто границы их рынка перестали расти. И тут неожиданно начинает играть решающую роль вопрос СОВМЕСТИМОСТИ. Никогда он не вставал прежде с такой остротой. Разработчики не привыкли "тащить" старые ОС, например. Действительно, зачем это тащить, когда компьютеры до конца 70ых продавались максимум числом 10 тысяч в год. Сейчас это просто смешная цифра. Выгодней было перелопатить все заново. Чаще можно заметить, что до 1975 года следующую модель компьютера или ОС создавал уже другой человек. И тут вдруг 80-ые показывают, что миллионные тиражи это реальность. Это серьезная заява к вопросу о всплытии проблемы СОВМЕСТИМОСТИ.

Если вы поднимитесь на несколько абзацев выше, то увидите, что все ПК создавались со встроенным интерпретатором Бейсика. Похоже на стандарт. А зачем людям Бейсик? Наверно они на нем пишут. Значит, у них есть код, который они желают без труда пускать на новом ПК. Можно что угодно думать про юзеров, про их симпатии к языкам или архитектурам, но они оплачивают ваш труд. Без них мы бы до сих пор сидели под штанами у военных, или писали никому не нужные проги, только лишь бы дисер защитился. Поэтому если бы вы писали ОС, то что нужно было бы сделать в первую очередь? Много версий. Все сводится к вопросу: "А ДЛЯ КОГО?", или "А ЧТО НАДО-ТО?". Batch processing и Бейсик вот две вещи, к которым привыкли юзера к началу 80-ых. Что у нас еще есть? Графика и диски. Тааак. Графика - дорого, полезно только для игр(тогда мультимедиа не было), для работы нужно много места, слабо распространено. Диски - дороже чем кассеты, но быстрее и надежней, благодаря Apple распространены. Тем более это согласуется с открытой архитектурой. Далее, сеть - дорого, не стандартно, мало кому нужно. Ну и конечно модем - запредельная скорость 1200 бод, дороговизна, узкая специфика. Ну вот и нарисовался конфиг ОС для дома для семьи. А он уже почти есть! CP/M. Дальше вы знаете. Через всю линию появляющихся продуктов проходит жирными буквами СОВМЕСТИМОСТЬ.

Вопрос совместимости стоял и во время разработки С++. Язык С++ обладает более длительной историей, чем Паскаль. Появившись в 1979 году, как "СИ с классами", он стандартизировался ISO в ноябре 1997 года! Почему же так долго?! И при чем здесь совместимость? Для начала, вот что говорит о языке секретарь комитета ISO/ANSI по стандарту C++, он же входит в состав комитета по стандарту SQL, он же главный редактор журнала C++ Report - это Herb Sutter:

  • 1. Not everyone agrees on what what "OO" means. Most would agree that inheritance and polymorphism are "OO" concepts; some would include encapsulation; a few might include exception handling; perhaps no one would include templates. Still, there are differing opinions.
  • 2. C++ is a multiparadigm language, not an OO language. It supports many OO features, but it doesn't force programmers to use them. You can write completely non-OO programs in C++, and many people do.
  • 3. No language is the be-all and end-all. Today, I'm using C++ as my primary programming language; tomorrow, I'll use whatever best suits what I'm doing then. C++ does not have a module system (complete or otherwise), it lacks other major features like garbage collection, and it has static typing but not necessarily "strong" typing. All languages have advantages and drawbacks. Just pick the right tool for the job, and avoid the temptation to become a nearsighted language zealot. :-)

Язык программирования вообще не представляет из себя чего-то фантастического. Если относиться к нему, как программе, то многое становиться понятным. Например, самая модерируемая тема - это VERSUS-ы. Как вы думаете, можно по одинаковым параметрам оценивать БАЗЫ ДАННЫХ и CAD системы? Сомневаюсь! Можно ли сравнивать танк и бомбардировщик? Вот почему наивно смотрятся наезды как "пасквилянтов", так и "сионистов". Давайте рассмотрим это поближе.

Паскалисты в основном упирают на скорость компиляции, простоту и безопасность типов в ПАСКАЛЕ. Их можно понять, когда читаешь статьи того же ВИРТА. Даже читая статьи Страуструпа(автора С++), понимаешь причину "жарости" споров. Как тот, так и другой, эти авторы языков, не обходятся без следующей фразы (перефразируя): "На моем языке пишут только серьезные пацаны!". Ребята, не делайте себе авторитетов. Это потенциально источник ложных данных(подчеркиваю! ПОТЕНЦИАЛЬНО!). Так же как и художественная литература и пресса.

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

Я взялся за тему безопасности программирования. Думаю, это касается как разработчиков систем распознавания, так и для авторов АРМ СКЛАД. Наверно, есть и другие "общности", но о них можно поговорить отдельно. Вернемся к С++ и языкам ему предшествующим.

Все языки(за редким исключением) создавались, как коммерческий продукт для решения поставленных задач(математика, списки, БД, отчеты и т.д.). С++ не был тому исключением. Было очень много кода, написанного на СИ, много программистов, решающих системные задачи, и выбравших Си. Становиться понятным, почему был выбран Си для базового языка. Тем более он вышел из той же лаборатории. Но это уже экономика. Для более подробной информации вы можете обратиться к книге "Разработка (дизайн) и эволюция С++", которая по-моему не переведена на русский.

Но потом произошло совсем другое. Развитие С++ пошло не по традиционному пути. Некоторые языки, в отличие от С++, перешли во владение коммерческим организациям. Пример - фирма БОРЛАНД и ее "паскаль", который уже давно не Паскаль, а язык сохранивший алголоподобный синтаксис. Что же произошло с С++? Его решили стандартизировать? Не совсем это. Произошло вот что.

Глядя на черновые стандарты С++(доступные в Сети), легко заметить тенденцию к принципиальному изменению целей языка. Давайте попробуем "догнать" их рассуждения. Обратим свой взор на "фичи". Зачем их добавляют в ЯП? Видимо, чтобы облегчить труд программеров или поддержать существующие технологии. Но есть одна гадость. Смотрите, какие бы фичи в языке не появлялись, их достаточно бывает только на первых порах. Техника не стоит на месте, технология предоставления API то же. И если не добавлять удобства, то язык забудут. Ведь язык, как программа продается девелоперам. Это странные люди. Они прекрасно знают, что эффективность ПО может быть разной:
  • - эффективность кода(размер)
  • - скорость
  • - требование к ресурсам вообще
  • - возможность портирования
  • - быстрота разработки
Девелоперы частенько выбирают последнее в качестве главного критерия. Поэтому не трудно догадаться, что нужно делать с языком, чтобы его покупали. Его нужно напичкивать новыми фичами! Если взять старый Паскаль, то это фигня какая-то, а не язык. Модульности нет, ООП нет и т.д и т.п. И дальше начинается долгий путь ПАСКАЛЕВСКИХ разновидностей. Вы спросите: "А что плохого-то?". Есть одна загвоздка. То что является фичей для одного, может быть багом для другого.

Вот возьмем концепцию ООП. Это фича, впервые появившаяся в СИМУЛЕ. Так вот, ОПП - это фича или баг? Вот, например, есть такой талантливый программер Aleх Stepanov, автор STL и концепции обобщенного программирования(Generic Programming), считает ООП в корне неправильным подходом. Вирт тоже придерживается "неласкового" мнения об ООП. Мне больше нравятся аргументы нашего земляка, Степанова. Он считает, что концепция ООП отнюдь не является "отражением" реальных объектов. Его можно понять, в реальности все по-другому. Продолжая свое рассуждение, Алекс приходит к следующему аргументу. Нужно "абстрагировать" алгоритм, а не объект. Потому что алгоритм поведения может объединять принципиально отличающиеся объекты.

Вот опять - начинаются военные действия. Опять "ООП vs ШАБЛОНЫ". Прошло много лет, а пришли ли эти умные дядьки к согласию? Нет. Вот и задумаешься, а что же эффективней? Что безопасней? Отказаться от ООП и вставить только шаблоны(templates)? Или отказаться от шаблонов и оставить ООП? Не знаю, может Страуструп и в чем-то был не прав, но я бы на его месте поступил также. Он включил в С++ и ООП и шаблоны. Пусть программеры сами выбирают, что им нужно. Благо, на практике, оказалось что обе эти концепции приятно дополняют и улучшают друг друга. В этом я успел убедиться на собственном опыте.

Давайте немного остановимся на шаблонах. Не всем паскалистам эта возможность известна. Итак, начнем с конца. Благодаря шаблонам С++ стал первым языком, который столкнул с вершины "The best for numeric" Фортран(точнее Фортран-90). Самое малое, математические программы на С++ опережают Фортрановских лидеров на 20%. Это прозвучало, как серьезная заява на язык для ученых. И они постепенно начинают на него переходить. Кстати, любителям БД. Существует несколько шаблонных библиотек для работы с СУБД на их же родном уровне. Например OTL(ORACLE and ODBC Template Library). Я не собираюсь сравнивать ADO или там другие компоненты Делфей для "прямой" работы с СУБД, с библиотекой OTL. Это не цель данной статьи. Завершая обзор современного состояния обобщенного программирования, скажу что количество библиотек шаблонов для разных областей(сети, мультимедиа, расчеты) растет как снежный ком. Это совпадает со словами Страуструпа, что девелоперы только сейчас начинают по-настоящему использовать обобщенное программирование.

Я специально начал с итогов(хоть и не всех) развития обобщенного программирования. Зная будущее, которое теперь настоящее, очень интересно смотреть назад, в прошлое. Итак, как же возникла идея обобщенного программирования? Как утверждает Алекс Степанов, в 1976 году, когда он еще жил в СССР, он отравился рыбой и попал в больницу. По себе знаю, там много времени, чтобы порассуждать. Именно тогда его посетила идея рассматривать алгоритм, как алгебраическую структуру. И далее он упорно продолжал работать над реализацией этой идеи в течение 15 лет. "I believe that iterator theories are as central to Computer Science as theories of rings or Banach spaces are central to Mathematics. Every time I would look at an algorithm I would try to find a structure on which it is defined. So what I wanted to do was to describe algorithms generically" - Alex Stepanov.

Проясню немного термин "теория итераторов". Итератор(от слова итерация) - хороший аналог слова "курсор", но не в плане отображения на экране. Имеется ввиду позиция и поведение в массиве(списке) данных. Дело в том, что первые алгоритмы, которые пришли в голову для общего определения - были алгоритмы работы с массивами данных. Т.е. списки, деревья, поиск в них, сортировка и т.д. Видимо, это было актуальней всего. Так вот, программист не знает, как и где организован массив данных. Он даже не знает массив это или это дерево. Это "знает" итератор. Он же отвечает за "вытаскивание" данных. Свое же "движение" итератор проецирует на арифметические операции. В результате, вы можете организовывать хранение данных как угодно и где угодно(память, винт, стример, сеть и т.д.). На исходный текст программы, использующую эти данные, это никак не повлияет. Это чистой воды абстракция. Приведу пример.

Допустим, у вас есть некий каталог, который хранит всякую всячину. И вот вам надо его распечатать. Только вот проблема: каталог разбит на части. Одна часть лежит на ленте(СТРИММЕР), другая на винте вашего сервака, а большой архив расположен на SQL-сервере вашего "головного" филиала. Вот она прелесть современных технологий, а отдуваться приходиться программистам. Безусловно можно написать простую иерархию классов, но нас интересует обобщенное программирование.

template void print (const TCatalog& dir) const 
                                    // передача параметра по ссылке(var в паскале)
{
   for(typename TCatalog::const_interator i = dir.begin(); // а вот и он! ИТЕРАТОР позиционируется в начало
       i != dir.end(); // понятное дело, надыть знать границы
       ++i ) // этот инкремент не что иное, как "впередЪ!"
            cout << *i; // печатаем.
}

Опа! Грузанул. Понимаю, не привычно. Разобьем объяснение на две части.

ЧАСТЬ ПЕРВАЯ "Шо енто такое?"

(Фигурные скобки не участвуют в обсуждении)

Первая строчка: template void print (const TCatalog& dir) const
Суть: template - говорит компилятору, что мы тут шаблон объявляем. TCatalog не какой-то конкретный тип, а параметр шаблона типа "CLASS".
Остальное означает следующее:
procedure Print(const dir: TCatalog); Так понятно?
Для любопытных. Все эти на первый взгляд не нужные const-ы говорят:
  • а) Что параметр не ДОЛЖЕН меняться
  • б) Что сама процедурка ничего не ДОЛЖНА менять.
А дальше за этим будет следить компилятор. И если мы надумаем что-то нагадить, то он нас поймает за руку и даст в морду. Ну если вас это смущает, то можете "гадить" так: template void print (TCatalog& dir) - и делайте что хотите!

Вторая строчка самая "страшная" для паскалистов: for(typename TCatalog::const_interator i = dir.begin();
Суть: TCatalog - это некий класс. А typename TCatalog - получает имя этого класса при компиляции.
TCatalog::const_interator - это значит вложенный тип. Т.е. const_interator - это тип "ничего_не_меняющего" итератора в типе TCatalog.
"Вложен" он потому, что итератор должен "знать", как ему двигаться в TCatalog.

Третью и четвертую строчки я пропускаю за их элементарностью.
Пятая строчка: cout << *i;
Суть: cout - это (Console OUT) стандартный поток вывода. А "<<" - это оператор, который не трудно догадаться что означает. "*" - это не умножить. Здесь немного придется пояснить.
Паскаль очень глупо ведет себя с памятью. Я об этом ниже расскажу. Так вот у него есть тип Pointer(нетипизированный указатель), который в сочетании с "OF" дает тип - указатель на что-то. Но чтобы обратиться к этому чему-то нужно применить оператор "ГАЛОЧКА" - "^", например PtrArray^[i]. Это называется разыменовать. Так вот в С++ "звездочка", то же что и "галочка" в Паскале. Понятно?

А разыменовывание итератора - суть вытаскивание текущей его позиции.

Понимаю, пример может показаться неверным. Я хотел продемонстрировать именно абстрагирование шаблонов так же, как и при ООП. Конечно, можно привести пример:

vector ArrayAnyType;

Что означает динамический массив элементов типа AnyType. Только в отличие от TList и от array of AnyType - он в несколько раз безопасней и быстрее. Но об этом позже. Вернемся к примеру с шаблоном процедурки print.

ЧАСТЬ ВТОРАЯ "Шо это дает?"

Этой процедурке можно передать любой массив и она его выведет на печать. Например:

typedef vector TArrayMyObject; // для удобства

   TArrayMyObject my_array; // объявляем переменную my_array

   print(my_array) // вИзИваем функцию

   // УСЕ!

Напомню о условиях задачи. Массив может быть ЛЮБОЙ и лежать он может и не в ПАМЯТИ, а сами помните где. Итератор позволяет мне об этом забыть. Давайте, черт побери, сравним!

type
   TBaseItem = class(TObject)
   public
        procedure Display; virtual;
   end;
   
   
procedure Print(const dir: TBaseCatalog); // TBaseCatalog это переделка TList под TBaseItem
var
  item : TBaseItem;
begin
     if(dir.Count = 0) then exit; // извините, привычка! Не обсуждается.
     // исключения пока не рассматриваем!

     dir.First;
     while(not dir.EOF) begin
           dir.Read(item);
           item.Display;
           dir.Next;
     end; // аля Модула-2
end;

//УСЕ!



Ну что? Можно кровожадно потирать ручки? Да нет. Не совсем. Тут есть такая гадость. А если вам потребуется создать массив элементов встроенного типа(integer, real)? Вы будете описывать тип с одним полем? Убого. Хотя кто-то может этим и хвастать. Только прога с шаблоном будет работать быстрее и памяти жрать меньше! Объясню:

  • 1) Скорость. Значит так, в теле цикла три вызова, транслируемые через таблицу виртуальных методов. При большом количестве итераций это становиться ощутимо. В случае же с итератором, его "движение" осуществляется через встроенную(INLINE) функцию. Но не стоит воспринимать встроенные функции, как панацею в борьбе за скорость итераций. Это ловушка. Многие используют фокус со встроенными функциями, думая что их программа не с того не сего начнет работать быстрее. Я тоже наступил на эти грабли. На самом деле встроенные функции полезно использовать в двух ситуациях. Первое - когда эта функция ОЧЕНЬ-ОЧЕНЬ-ОЧЕНЬ проста типа return value; или return left

  • 2) Ресурсоемкость. Дело в том, что в Делфи тип class реализован через одно очень загадочное место. Связано это с большой нелюбовью паскаля к памяти. Работа с последней в паскале реализована по принципу "Не доверяй - не придется проверять!". Тип указатель в паскале создан только для того, чтобы указывать на что-то в динамической памяти(куче). Он создан, как шлюз между статической памятью паскаля и кучей. Странно, но зачем-то разработчики языка оставили возможность приводить целое к указателю. Такое понятие как ссылка не знакомо паскалю. Это вносит путаницу. Когда мне пришлось писать небольшой шлюз между отечественным протоколом АП-70 и Vines IP, я вынужден было это делать на Си. Паскаль просто "не канает" в таких задачах. Так вот я никак не мог разобраться с сишными указателями и ссылками. Зачем это все? Много лет работы на Паскале сказались. Дык вот, один мой знакомый (спасибо ему огромное) подошел и сказал: "Че ты мучаешься? Забудь про Паскаль. Думай, как в ассемблере." И тут я проснулся :o) Ссылка - это адрес, алиас(база в асме), или, грубо говоря, замороженный указатель, при обращении к себе автоматически разыменовывается. В основном это используется при передаче параметров. Вернемся к типу class в Паскале. Оный представляет собой помесь "сишных" (человеческих) указателя и ссылки. Т.е.
    • - он может указывать только на кучу ( form := TForm.Create(Self); )
    • - он автоматически разыменовывается ( form.Caption := 'Caption'; )
    • - но "забывает" сделать это при присваивании ( form2 := form1; //указывают на одно и тоже)
    • - и не умеет двигаться.
    В результате, если вы пишите класс "комплексное число", а затем решаете создать массив чисел, то array [1..10] of TComplex; будет на самом деле занимать в памяти 4*10 байт плюс выравнивание. Т.о. вы может быть хотели именно массив ТОЛЬКО КОМПЛЕКСНЫХ чисел, а не указателей на них. Но вместо этого, после инициализации, у вас будет израсходовано (4*10 + 10*sizeof(TComplex)) байт памяти. Короче сами считайте.

Но скорость и ресурсоемкость не главное в нашем обсуждении. Самое главное в шаблонах - это их удобство безопасного использования. Фирма Борланд была тоже под впечатлением от обобщенного программирования и STL в частности. Поэтому они даже не пытались создать ничего подобного в VCL. А для пасквилянтов даже придумали динамические массивы (аналог vector) и зафуздячили TList.

Но мы отвлеклись. Итак, разговор начался с того по какому пути начал развиваться С++. Я уже упомянул, что в каждом языке есть "фичи". Вы это знали и без меня. Напомню лишь, что каждая фича имеет "срок жизни". Т.е. это не значит, что фича становиться не нужной(хотя и это возможно), это лишь значит, что возможностей фичи(каламбур) может не хватить через некоторое время. Возникает мысль - А почему бы не создать возможность создавать фичи самому программисту? Вот это и попытались сделать те, кто участвовал в доработке детища Страуструпа. Это была первая попытка.

Я не собираюсь объяснять термин "multiparadigm" по отношению к С++. У меня такое чувство, что вы и так это знаете. Мне хотелось бы в первую очередь остановиться на анализе безопасности "безопасного" Паскаля и "опасного" С++. Фанатики-пасквилянты могут пропустить этот анализ, т.к. он безусловно будет не в пользу Делфей.

Начнем с простого, с того о чем мы уже говорили. Шаблоны и STL. Многие из вас, конечно, знают, что группа разработчиков Делфи упорно не желает вводить шаблоны. Почему? Да уж не потому, что это не нужно. Я остановлюсь на этом в конце рассказа о безопасности обобщенного программирования. Параметризированные типы, т.е. шаблоны, не следует рассматривать без следующих возможностей С++:
  • 1) Вложенные типы(описание типов в любом месте программы).
  • 2) Переопределение операторов.
Без этих двух вещей обобщенное программирование выглядело бы не столь эффектно. Кстати, когда автор STL, Alex Stepanov, начал реализацию своей библиотеки он выбрал С++, хотя мог бы(по его словам) написать ее и на АДА и на Scheme(разновидность объектного ЛИСПА). Вот его слова: "In other words, I know what I want to say. I can say it in C++, I can say it in Ada, I can say it in Scheme. I adapt myself to the language, but the essence of what I am trying to say is language independent. So far, C++ is the best language I've discovered to say what I want to say." Алекс вообще считает, что если в языке нет шаблонов, то он не достаточно удобен в применении. Но вернемся к безопасности.

Сколь бы не была далека ваша программа от математики и расчетов, вам все равно приходится иметь дело с массивами. А точнее с динамическими массивами. В паскале есть три "техники" работы с динамическими массивами:
  • 1) Древнейшая.

                const
                   MaxSize = //что-то
                type
                   TMyItem = record
                    //....
                   end;
                   TMyArray = array [1..MaxSize] of TMyItem;
                   PMyArray = ^TMyArray;
                //...
                // где-то в программе
                function GetMyArray(const s: TMyStream) : PMyArray;
                begin
                     if (s.size<=MaxSize)
                        then Result := AllocMem( s.Size )
                        else Result := nil
                end;
    

  • 2) Открытые массивы.

               procedure StuipidToZero(var A: array of integer);
                var i : Integer; 
                begin
                     for i:=0 to High(A) do A[i]:=0
                end;
    

  • 3) Динамические массивы.

                var
                  A : array of integer;
                begin
                    SetLength(A,2);
                    A[0] := 1;
                    A[1] := 2;
    

Пока хватит. Все выглядит вроде бы мирно и спокойно. Поедемте дальше, господа. "Морды будем после бить", как поется у Высоцкого. Мааааааленькая добавка. Давайте посмотрим, как можно организовать работу с "универсальными" динамическими массивами. Т.е. массив, где могут лежать элементы разного типа. Ну например, вы реализуете некий класс, который ведет себя как массив и предоставляет различные удобства, типа сортировки, поиска и прочая.

  • 1) Самый уродский.
                 TMyCustomArray = class
                  //.....
                  FArray : array of pointer;
                  //.....
                  procedure Sort(var Buf);
                 end; 
    
    Короче конкурс "супер-абстракция" на тему - кто вставит больше преобразований типов.

  • 2) Использовать TList. Не имеет никакой разницы с методом "Самый уродский".

  • 3) Использовать Variant или TObject и RTTI.

Вот теперь "будем морды бить". Когда Страуструпа спрашивают, если бы сейчас он начал разрабатывать язык - то "получился" ли бы С++? Нет! Конечно, нет - всегда отвечает он. В первую очередь он бы не уделял бы столь много внимания вопросу совместимости с Си. Но главное, он бы "упростил" типы. Т.е. вместо массива(int array[size]) он бы включил только параметрические типы, т.е. в данном случае vector. Почему?
Во-первых:
архитектурно-независимый и портируемый код. Большинство контейнеров в STL определено как template > Это означает, что второй параметр, АЛЛОКАТОР, по умолчанию равный стандартному, можно "переопределить". Т.е. вы можете написать свой Аллокатор, который, например, работает с диском через буфер. Проще говоря, массив лежит не в памяти, а на диске. Такая маленькая виртуалка. И это НА ЛЮБОЙ ПЛАТФОРМЕ, НА ЛЮБОЙ МОДЕЛИ ПАМЯТИ!
Во-вторых:
ЖЕСТОЧАЙШАЯ "защита" типов. Если вы пишете vector, то это массив именно этого типа. v[1] = OtherType // не проканает. Компилятор скажет вам все, что он думает о вашем стиле программирования. Преобразование типа можно запретить для этого класса. Далее. Когда вы описываете массив, то в нем хранятся не указатели, а сами классы(конструкторы которых уже вызваны). Когда вызывается деструктор массива- вызываются деструкторы всех его элементов. Короче, пользуясь контейнерами из STL для написания универсальных динамических массивов, вы обеспечиваете себе защиту даже больше, чем array [1..100] of MyType; Почему? См. ниже.
В-третьих:
Полная обработка всех исключений, которые могут возникнуть при работе с массивами. В случае, например, исключения выделения памяти, вызывается деструктор контейнера, т.е. вызываются деструкторы всех тех элементов, которые "успели сконструироваться", затем передается управление пользовательскому обработчику исключений, если оный есть.
В-четвертых:
Поразительная гибкость. Вы можете, используя механизм Частичной или Полной Специализации, перекрывать и менять поведение контейнера в тех или иных ситуациях. Например, поменять стратегию REALLOCATE в контейнере vector.
Эти четыре самые важные из множества преимуществ, обеспеченных применением обобщенного программирования и STL, которая является ЧАСТЬЮ СТАНДАРТА С++. Теперь посмотрим другие фичи, обеспечивающие более безопасное программирование на С++.

Еще раз о шаблонах. Я упомянул о том, что целью С++ является технология программирования, а также технология "создания фич". Это передвигает С++ из области "специальных" языков, в область языков общего назначения. Поэтому я не сравниваю фичи С++ с фичами Делфи или Ява. Последние два являются классическими представителями "специальных" языков. Я лишь уделяю внимание, как не специфичный, самый критикуемый и самый популярный язык обставляет ту же Делфи в том, чем обычно хвастают ее создатели - безопасность программирования(безопасность типов и работы с памятью).

Вот сейчас много говориться о "сборке мусора", "счетчике ссылок" и т.д. "Бедные" программисты на Делфи или Ява вынуждены ждать этой поддержки в языке или качать библиотеки компонентов, реализующих это, и переписывать свои проги. С++ же предоставляет разработчикам возможность самим писать такие вещи как "сборщик мусора"(что и сделали в МС, поставляя с VC++ необходимые шаблоны).

В STL, для таких целей, есть простенький шаблон указателя AUTO_PTR. Он делает невозможным некоторые стандартные ошибки программиста при работе с указателями. Например, самая распространенная:

int *p1 = new int[10]; // первый динамический массив
int *p2 = new int[20]; // второй динамический массив
p1 = p2; // мусор из 10 целых

существует 2 решения:

   //НОМЕР РАЗ
   typedef vector IntArray;

   IntArray p1(10);
   IntArray p2(20);
   p1 = p2; // освобождает память и создает копию p2

   // НОМЕР ДВА
   // В случае если у вас отнюдь не массив, а просто указатель на класс, лучше 
   // использовать auto_ptr
   auto_ptr p1 = new MyType;
   auro_ptr p2 = p1;

Стандартная ошибка двойного ownership-a. В обычном случае, программа попытается вызвать деструктор MyType два раза. В случае же с auto_ptr - деструктор вызовется только однажды - при удалении p2. Я не привожу примеры реализации более полезных УМНЫХ указателей, реализующих сборку мусора и правильную работу с ресурсами вообще. Таких шаблонов в Сети полно, можете в конце концов стянуть сырцы от МС.

Почему же это стало возможным в С++, и не светит Делфинистам? В первую очередь потому, что в С++ типы определенные пользователем ведут себя, как встроенные. Это стало возможно благодаря переопределению операторов, которое в сочетание с шаблонами и объявлением typedef, позволяет, не меняя сырцов программы, портировать ее на любой камень хоть с 128-разрядной арифметикой и суперфантастичной реализации работы с памятью.

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

Миф 1:

Поразительно, но многие считают, что применение шаблонов заставит ожидать компиляции вашего проекта часами. На чем же это все основано? Во-первых: зависть, лишь бы ляпнуть че-нить плохое про другого. Во-вторых: наличие шаблонов "заставляет" компилятор знать всю иерархию классов и других типов. Но компилятор С++ и так делает это и не только с шаблонами, но и с обычными классами. Это позволяет строить очень эффективный код. Пример из Страуструпа:
         try{
         }
         catch(...) { /*код лежащий здесь компилится всегда*/ }  
         catch(std::exception& e) { /*код лежащий здесь компилится только если убрать catch(...)*/ }
         catch(std::bad_cast) { /*код лежащий здесь не компилится никогда, т.к. bad_cast наследник exception*/ }
Поэтому "знание иерархии классов" не замедляет компиляцию шаблонов. Возник этот миф, скорее всего, вот почему. (ниже перечисленное соединено союзом И)

а) большая вложенность шаблонов.
                template< class C = traits_1< traits > >
                class ComplexTemplate : public interface, protected implementation field1;
                  tree field2;
                  // и т.д.
                }
В результате первой компиляции будет строиться большое дерево обхода реализаций вспомогательных шаблонов. Но это лишь говорит о безграмотном проектировании.

б) Не использование forward объявлений. Т.е. включение большого кол-ва директив #include "header.h". В сочетании с вышеуказанным приводит к чудовищным последствиям. Но обычно это происходит с программистами вроде меня, т.е. те кто переходит с других языков, например с Паскаля или Си.

Но даже при наличии этой "гадкой парочки" компиляция займет от силы пару минут. Это плохо, но это прямой результат тяжелого наследия процедурных языков. МС, например, решили эту проблему просто в своем C#, они нагрузили компилятор знанием namespace (пространство имен). Вы пишете using namespace std, а компилятор сам ищет нужные файлы. Недостаток этого метода - нужно несколько проходов по компилируемому файлу.

Завершая обсуждение МИФА, в большинстве случаев критикующий сильно преувеличивает медленность компиляции.

Миф 2:

"У шаблонов нет будущего, т.к. само применение их сложно, а область их применения узка."

Обобщенное программирование - очень молодая концепция. Молодая и интересная. В чем-то она повторяет путь концепции ООП. Почти 15 лет зрела эта концепция в умах программистов. Со времени первой версии ООП ЯП - СИМУЛЫ(1962 год) до первого коммерческого успеха ООП - языка Smalltalk(1972) проходит 10 лет. До идеи множественного наследования проходит еще 13 лет. Это очень-очень долгий путь. Возможности эти трудно переоценить, но легко облаять. В то время как многие критикующие забывают, что COM и вся идея компонентного программирования возникла на благодатной почве ООП.

Обобщенное программирование витало в головах многих программеров. И было много попыток реализации этой идеи, как вы помните по началу статьи. И вот, наконец, только в 90х мы видим самое эффективную реализацию этой идеи в языке С++. И что же в результате?

С шаблонами борются, шаблоны ругают. Такие "полезные" штуки, как CORBA или ODMG-93(стандарт аналогичный COBRA, но в области "объектных" БД) "не понимают" шаблоны С++, определяя собственные Sequence и CORBA::String. Почему? Ну прежде всего это вопрос к авторам этих стандартов. Но я лишь могу согласиться с Алексом Степановым. Здесь кончается обычное программирование и начинается MOP - money oriented programming. Мы уже писали об этом в статье "Деструктивный маркетинг". Обязательно прочтите эту статью, написанную несколько месяцев назад, потому что сейчас мы частенько наталкиваемся на схожие рассуждения в различных "серьезных" журналах. К "МОР" мы еще вернемся. Зачем? Просто сам принцип обобщенного программирования противоречит принципам Деструктивного маркетинга.

Видите ли, концепция обобщенного программирования - это compile-time концепция. Т.е. если вы захотите распространять свою библиотеку шаблонов, то вам придется распространять ее вместе с исходниками. По-другому просто нельзя. Одно дело, принцип OpenSource, а другое дело, когда вы не хотите раскрывать своих исходников, предоставляя лишь API. Почему люди не хотят раскрывать свои сырцы? Причин много, мы их обсуждаем в статье про Деструктивный маркетинг. Не буду повторяться.

Но посмотрите реально на влияние концепции обобщенного программирования. Люди хотят этого. И разработчики языков вынуждены идти на уступки. Так в Делфях были введены динамические массивы, в Ада был написан аналог STL; новый стандарт языка СИ - С99 ввел некоторое "подобие" STL(трудно это делать не поддерживая шаблоны); в новых версиях Ява также обсуждается создание некоторых обобщенных контейнеров - Java Generic Library; разрабатывается новый принцип компиляции шаблонов, чтобы можно было поставлять библиотеки без исходников. В новом стандарте С++, если оный выйдет, возможности шаблонов будут расширены, а сами шаблоны вытеснят некоторые встроенные типы. Перечислять можно долго. Мое мнение нетрудно предсказать - шаблоны это гуд.

Миф 3:

"STL is not thread safe". Кому не нравиться старый HP STL - качайте новый SGI STL. Этот STL thread safe teamplate. Вообще поддержка многопоточности не является стандартной фичей в С++. Разработчики не думали, что развитие компьютеров будет происходить такими темпами. Но тем не менее, технологически подход в создании языка, позволил сделать его гибким к такого рода изменениям. И это продемонстрировал Alex Stepanov, переписав STL с учетом многопоточности. Существует масса всякого рода шаблонов расшареных АЛЛОКАТОРОВ, которые "знают" как безопасно распределять память между нитками. Эти Аллокаторы можно смело вставлять в старый HP STL(январь 1997г) и оный станет thread-safe.

Недостатки: все они касаются STL, а не концепции обобщенного программирования.

Минус 1 : Нет шаблона для своего дерева. Т.е. мне нужно самому писать шаблон односвязных списков или троичных деревьев или кластерного массива.

Минус 2 : Нет шаблона "адаптера сортировки". Т.е. все контейнеры поддерживают сортировку. Там можно задать условие сортировки. Но одновременно интерпретировать для одной структуры несколько сортировок контейнеры не могут. Приходится писать свой шаблон адаптера.

Минус 3: Нет функций для организации неуникального поиска(find_if не канает - медленно), например, партиальный поиск(partial search - mask search ) или вычисление "расстояния" между ключами, поиск синонимов и прочее, что уже давно имеет численную реализацию на Си, но не включено В STL.

Кстати, Степанов говорит, что при разработке почти все что я перечислил в МИНУСАХ, хотели включить в STL. Но их остановил Страуструп. Почему? Смотри статью "Деструктивный маркетинг". Будет приятно увидеть преобладание принципа OpenSource - это лишь будет способствовать распространению обобщенного программирования. Повторюсь, шаблоны(Compile-time полиморфизм) это гуд. Жаль, что девелоперы это начали понимать лишь спустя 3 года. Но лучше поздно, чем никогда.

Перейдем к не менее интересной части языка С++ - исключениям. То, как они организованы в С++ в сочетании с "сишной" моделью ООП, позволяет еще больше обезопасить программера. Так как и сишная концепция ООП и исключения кажутся многим "чересчур сложными" опять приведу слова Herb Sutter: No feature is strictly "necessary" insofar as any program can be written in assembler (or lower).

Начнем с принципиальных отличий в модели обработки исключений в С++ от Делфи. И какие это порождает гадости. В первую очередь, Борланд ввел некоторые ограничения на перегенерацию собственных исключений. Вырезка из Help:
1) You cannot rethrow an operating system exception once the catch frame has been exited and have it be caught by intervening VCL catch frames.
2) You cannot rethrow an operating system exception once the catch frame has been exited and have it be caught by intervening operating system catch frames.
3) You cannot use "throw"(аналог Делфийского raise) to reraise an exception that was caught within VCL code.

Ну ладно, нельзя так нельзя, хотя жаль. Рассматривая модель ООП в Делфях и модель ООП в С++, легко прийти к выводу, что функционально модель С++ шире, и поэтому Борландовский Буилдер легко "глотает" делфийский VCL. Давайте посмотрим только на те плюсы ООП С++ в сочетании с обработкой исключений, которых нет в Делфях. А уже потом минусы этой же модели.

Помнится как много критиковали С++ за множественное наследование и за всевозможные public, private, protected, freind. Сейчас вообще-то тоже критикуют, но в том же Делфи теперь все это есть, за исключением "друзей". Итак, ООП в С++! Подробнее с ним вы можете ознакомиться в любой книге Страуструпа про С++. Там же вы сможете узнать, почему он эту модель сделал ближе к модели СИМУЛЫ, а не SmallTalk-а. Опуская подробности, скажу, что нужна была модель, которая бы обеспечивала совместимость с Си(не беспокойтесь, СИ не ООЯП). Это была одна из "маленьких" целей. Кстати, отсюда же и произошли все недостатки ООП в С++.

Начнем с того, что т.к. С++ язык общего назначения, то и его модель ООП должна быть как можно более общей. Поэтому программисты предпочитающие другой язык частенько попадают в ловушку типа, "Я это не использую, значит это не надо". Поверьте, вами программирование не заканчивается. Есть люди, которые наоборот благодарят за то, что эта фича была введена. Так что хватит об этом. Первое и самое главное отличие, которое бросается в глаза, это отношение классов к памяти. В любом ЯП есть три типа памяти:
  • - статическая, формируемая из данных на этапе компиляции.
  • - автоматическая, в которой хранятся локальные и временные переменные, а также параметры процедур и функций. Иными словами - это стек.
  • - динамическая. Память, которую требует программа на этапе выполнения(Run-time). Еще ее называют кучей. Ну, как говориться, надежное дело кучей не назовут. Поэтому люди частенько спорят, называя, например, динамическую память совокупностью куч. Хе-хе!

В С++ классы могут находиться в любой памяти, из перечисленных выше трех. В Делфи классы(объекты) могут располагаться только в динамической памяти. Это, как я уже объяснял, связано с нелюбовью Паскаля к работе с памятью вообще. Из этого вытекает следующее отличие. Все конструкторы и деструкторы классов Паскаля вызываются явно.
      object := TMyObject.Create. // где-то в начале
      //....
      object.Free; // где-то в конце
С одной стороны хорошо. Все ясно, как никогда. Но это специфика Паскаля заставляет программера делать уйму работы, и порой ошибаться. Частенько бывает необходимо иметь "неявный" вызов или конструктор "по умолчанию". Конструктор класса С++, например, вызывается как только встречается описание экземпляра (переменной) класса. И, соответственно, деструктор вызовется, как только класс "выйдет из области видимости".
 // пример номер РАЗ
 bool f(){ // некая функция
     TComplex c; // экземпляр класса
  // что-то делаем
     return true;
 }  

Здесь фигурные скобки(да и вообще в С++) показывают область видимости. Объявив локальную переменную, мы вызвали конструктор_по_умолчанию класса TComplex. А перед выходом из функции вызывается деструктор этого класса. Если говорить системным языком, то деструкторы вызываются при "раскручивании" стека. Т.е. даже если произойдет исключение вызовется деструктор класса TComplex.
 // пример номер ДВА
 const TComplex f(){
    TComplex nums[100]; // локальный массив - вызывается 100 раз конструктор TComplex
    TComplex sum; // конструктор вызовется один раз

    for(int i=0; i<100; i++) sum += nums[i]; // суммируем массив

    return sum; // сумма всегда равна нулю
 } 

Здесь чуточку посложнее. Ну, понятно, что конструктор TComplex обнуляет свои поля(члены-переменные). Но что будет если произойдет исключение на конструировании 3-го элемента массива? Вызовется только деструктор nums[0] nums[1], т.е. единственных(первый и второй по условию задачи) сконструированных экземпляров класса. Не будет вызываться деструкторы остальных элементов массива и деструктор переменной SUM. Т.к. до них "дело" не дошло! По-моему, это удобно - вам не кажется?

Ну теперь самое интересное - динамическая память. Тут еще проще - у указателей конструкторов и деструкторов нет. Но, повторюсь, это у встроенных типов. Чтобы вызвать конструктор у указателя надо воспользоваться оператором new. В случае же удаления - оператором delete.

TComplex* c; // переменная указатель на тип TComplex - ниче не вызывается.
c = new TComplex(1,1); // выделяется память под TComplex и вызывается его конструктор с параметрами.
delete c; // освобождаем память предварительно вызвав деструктор TComplex

Вот здесь работа с классами похожа на Делфийскую работу. Похожа-то, похожа - да не совсем.

Во-первых: как вы успели заметить new и delete - это операторы. Значит их можно переопределять. Значит, где захочу - там и будут лежать мои классы. Так можно организовать несколько куч, даже не имея "много-кучевого" менеджера ОС. Я позже опишу, как это влияет на безопасность.

Во-вторых: здесь всплывает понятие "ВРЕМЯ ЖИЗНИ КЛАССА" и то, как обрабатываются исключения в конструкторах и деструкторах.

Рассмотрим это поближе. В Делфи время жизни класса таково:
  • Рождение: Класс начинает свое существование сразу ПОСЛЕ окончания работы КОНСТРУКТОРА(вызов AfterConstruction).
  • Смерть: Класс заканчивает свое существование сразу ПОСЛЕ окончания работы ДЕСТРУКТОРА(вызов BeforeDestruction).
В С++ немножечко по другому:
  • Рождение: Сразу ПЕРЕД телом конструктора.
  • Смерть: Сразу ПОСЛЕ тела деструктора.
Это несколько меняет работу с конструкторами/деструкторами родителями и конструкторами/деструкторами членами.
Вот С++:

   class TChild : public TMama,TPapa{ // :o)
     TMemberOne member_1_;
     TMembarTwo member_2_;
   public:
     TChild() { cout<<"TChild created!"; }
   }

Порядок конструкторов будет следующий: TMama, TPapa, TMemberOne, TMemberTwo и только потом вызовется ТЕЛО конструктора TChild. Это логично и похоже на правду. Действительно, когда мы можем получить доступ к методам и полям(переменным класса) родителей и классов-членов(конкретных классов)? Мы можем получить этот доступ только, когда они сконструированы. И это лучше оставить на совести компилятора, чем надеяться на программера. Но иногда и он должен отрывать пальцы от "мыша".

   class TChild : public TMama{
         TMember* member_;
   public:
      TChild(const int other_mama, const int other_member) : TMama(other_mama), 
                                                             member_(new TMember(other_member)) {}
      ~TChild() { delete member_; } // будем знакомы я деструктор
   }

Как видите список инициализации(то что идет после ":") может формировать сам программер. Это нужно делать только в двух случаях:
  • 1) Когда нужно вызвать другой конструктор - не по умолчанию. Приятная возможность? Особенно, когда у вас масса разных конструкторов.
  • 2) Когда у вас есть динамическая инициализация переменных класса. В примере, это указатель member_, которому мы выделяем память и вызываем нужный конструктор.

Ну, пункт первый думаю объяснять не стоит. А вот на второй лучше обратить внимание(даже сишникам).
Я специально допустил там структурную ошибку. АКЦЕНТИРУЕМ ЕЕ!

   class E: public A,B {
     C* c_;
     D* d_;

   public:  
      E(); // реализацию см.ниже
     ~E() { delete d_; delete c_; }
   }

   E::E() // конструктор класса E
   try
      : A(1), B(1), c_( new C ), d_( new D ) // список инициализации
   
   { //начало тела конструктора
     cout<<"Constructor body";
   } // конец тела конструктора
   
   catch(...){ // ловим любое исключение
        A::~A();
        B::~B();
        delete c_;
        delete d_;
   }
   

Непривычное написание, не так ли? Да, в Делфях нельзя ВЕСЬ процесс конструирования поместить в блок try except.

Но давайте сначала займемся допущенной ошибкой, а потом я расскажу зачем это надо. Ошибка эта происходит не из-за того, что человек не знает как работает try...catch. Она происходит из-за того, что человек не знает как компилятор работает с исключениями в конструкторах; и плюс еще, выбрана не правильная стратегия обработки ошибок.

По старой привычке, корни которой уходят в далекие-далекие времена, люди стремятся "вытащить" кричащий об ошибках класс. Т.е. они стремятся урегулировать ошибку "на месте", не призывая к участию пользователя. Но это справедливо лишь тогда, когда исключение лишь передача управления, но не ошибка. В остальных же случаях лучше придерживаться следующей тактики:
- Получил исключение? Освободи касающиеся этого исключения динамические ресурсы и перегенери это исключение в том же или ином виде. Смерть свою исключение должно принимать на более высоких уровнях программы, в диспетчерах, интерфейсных классах и т.д. Но помните, на всякое правило есть свое исключение ;-)

Оставим старые привычки в покое и посмотрим как работает С++. Он работает по старой привычной схеме. Если происходит исключение, то начинается раскрутка стека, и все классы, успевшие нормально сконструироваться, вызывают свои деструкторы. В нашем примере:

: A(1), B(1), c_( new C ), d_( new D ) // список инициализации

Если произойдет исключение во время "B(1)" - то вызовется деструктор класса A. И стек будет дальше раскручиваться, пока не встретит блок try...catch. Т.е. С++ "знает" что у нас правильно собралось, а что вообще осталось только в мечтах. Компилятор-то знает, а вот знает ли это программер, ставящий блок try...cath в нашем примере? Нет! И тут даже функции IsOk не помогут.

Но как же быть с динамическими ресурсами? Спросите вы. Все очень просто:

   E::E() // конструктор класса E
   try
      : A(1), B(1), c_( NULL ), d_( NULL ) // список инициализации
   
   { //начало тела конструктора
     try{ 
         c_ = new C;
         d_ = new D;
         cout<<"Constructor body";
     }
     catch(...){
         if(c_) delete C;
         if(d_) delete D;
         throw;
     }
   } // конец тела конструктора
   
   catch(...){ // ловим любое исключение
        throw E_ErrorCreate();
   }

Видно, что я использовал блок try...catch только для "перевода" одного исключения в другое. И назначение этого блока только такое и никакого другого. Использование его в других целях может привести к гадостям, поэтому в некоторых С++ компиляторах(фирмы Борланд например) эта возможность от греха подальше убрана. Вы еще не заскучали?

Привел я этот пример не для демонстрации возможностей блока try...catch, а для того чтобы показать как С++ сам делает безопасным процесс "конструирования" класса. В Делфи все это ложиться на хрупкие плЭчи программера. Кстати о Делфях, я там не нашел аналог функции С++ - uncaught_exception() - показывает статус стека исключений. Т.е. есть щас необработанное исключение, или говоря системным языком стек сейчас раскручивается нормально? Применение? Пожалуйста:

      Transaction::~Transaction() // деструктор класса "Транзакция"
      {
        if ( uncaught_exception() ) Rollback();
        else Commit();
      }

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

Рассмотрим на последок "дружественные" отношения С++ к памяти. В первую очередь замечу, что в случае исключения в конструкторе - память не выделяется, а вернее освобождается.

TObject* obj = new TObject; // что будет если конструктор TObject выдаст исключение?

Фича в том, что оператор new уже выделил память для экземпляра класса TObject, а тут ррраз! И исключение! Что делает С++? Он тут же освободит память - не надо ставить блок try...catch. Все сделает С++. Ну, как говориться, приятная неожиданность. Но не надо уж совсем полагаться на язык. Например:

    byte buffer[100];
    TObject* obj = new(buffer) TObject; // стандартный оператор размещения

Здесь С++ не будет за зря дергаться, если произойдет исключение в конструкторе TObject. Раз уж это ваш буфер - то сами и с ним разбирайтесь. Справедливо? По-моему, да.

Но, оставим юношеский восторг и перейдем к сурьезным вещам. Переопределению операторов new и delete. Зачем это надо?

1) Работа с несколькими "собственными" кучами. Например, все покупатели складываются в одну кучу. А поступаемые товары в другую.

      class TCustomer {
            static THeap* heap; // указатель на менеджера кучи - общий для всех TCustomer
             // ...реализация
      public:
            void* operator new(size_t size) { return ::operator new( size, heap->get_free() ); }
            void operator delete(void* ptr){ heap->set_free(ptr); }
      }
      // аналогично для товаров
      // ...
      // а в завершении
      THeap* TCustomer::heap = new THeap(max_size_customer);
      THeap* TGoods::heap = new THeap(max_size_goods);
      // ...
      // где-то в программе
      TCustomer* c = new TCustomer("Иванов"); // пойдет в кучу для покупателей
      TGoods* g = new TGoods("Булка"); // в кучу для товаров
      // ...

Как видите осталось только реализовать менеджер кучи, что в рамках С++ вещь простая и ведущая себя незаметно (как встроенная фича). Можно так извернуться в Делфях? Нет! Что это дает нам простым смертным (Гы! "Это дубли у нас простые"(С)Стругацкие)? Если кто-нить из вас хоть раз заглядывал в реализацию куч или читал Кнута, то они представляют что такое фрагментация памяти и каким боком это может выползти наружу. Представьте, что после многочисленных удалений и вставок в динамической памяти образовались зазоры размером, ну допустим, по килобайту. Таких зазоров штук 100 и все они находятся между занятыми участками памяти. И вот ваша прога говорит: "Дайте мне 2 кило под массив целых". А менеджер кучи посылает ее куда подальше "Нету! Отвали!". Но как же ведь 100 кусков - это 100 килобайт свободно! А мне-то надо всего два. Старая, как мир проблема. У Кнута и во многих других умных книжках много написано решений этой проблемы. Но чем эффективней алгоритм - тем медленней память(либо операция выделения, либо освобождения). Поэтому здорово иметь кучу, в которой все куски(выделяемые и освобождаемые) одного размера. Т.е., например, массивы будут вставляться с одного конца, а одиночки с другого. Кстати, для массивов можно тоже выделить кучу. ТОЛЬКО НЕ ПЕРЕУСЕРДСТВУЙТЕ! Крайности еще никого до добра не доводили. Да и еще, многокученность решает попутно и вторую проблему - безопасности нарушения границ.

2) Помимо "многокучности", оператор new предоставляет вам возможность "виртуального" размещения объекта. Например в файле, в Сети, где вашей душеньке будет угодно. Это тоже недоступно в Делфях.

3) В сочетании с вашими же "умными" указателями - легко организуются сборщики мусора. И опять же вести они себя будут, как встроенные фичи. Вот она прелесть технологического подхода, когда программеру предоставляется "технология создания фич". Специализированные языки, такие как Делфи, Ява и т.д

4) У некоторых может возникнуть законное желание делать так, чтобы переменные, создаваемые динамически, тоже удалялись автоматически в момент выхода их из области видимости. Для этих людей создан маленький полезный шаблончик auto_ptr. Он делает массу полезностей, о которых мечтают разработчики под Делфи или Явой. Но! Я могу предложить еще кое-что в добавок к auto_ptr. Воспользовавшись переопределением new и delete вы можете ЗАПРЕТИТЬ создавать указатель на БЕЗОПАСНЫЙ тип. Например:
      template 
      class static_ptr{
        T* ptr;
        void* operator new(size_t) {};    // ЗАПРЕТ
        void* operator new[] (size_t) {}; // ЗАПРЕТ
        void operator delete(void*) {};   // ЗАПРЕТ
        void operator delete[] (void*) {};// ЗАПРЕТ
      public:
        // что-то делаем
      }
      Объясню. Операторы-пустышки описаны в PRIVATE секции класса. Тем самым если
      компилятор встретит такие:
        typedef static_ptr s_ptr_int;
        s_ptr_int *p1 = new s_ptr_int;     //ошибка!
        s_ptr_int *p1 = new s_ptr_int[100]; //ошибка!

Да! Встретив такие строчки, он вас обругает и по рукам надает. Но не пугайтесь т.о. не надо запрещать все. Т.е. если вы в вышеуказанном шаблоне не опишите операции по движению указателя(+,-) - то этих операций он и не будет делать. Но некоторые вещи в С++ создаются по умолчанию. И вы, как грамотный разработчик можете на это повлиять, запретив умалчиваемые функции. Вы можете запретить все что можете переопределять. Можете запретить присваивание, преобразование к некоторому типу, неявное преобразование, разыменовывание и т.д. Т.е. предпринимать все действия повышающие безопасность и эффективность кода. Вы изменяете язык в рамках своей проги. Это, конечно же, не доступно в Делфях.

Я постарался перечислить только те фичи, которые я уже использовал на "живых" проектах. Это не выдумки из книжки. Это не просто слова для галочки в колонке "Преимущества С++". И, естественно, я перечислил часть фич из области безопасного программирования. И вы знаете, парадокс, но менее читабельный для неподготовленного человека синтаксис становится более читабельным в сравнении с Паскалевским, в случае когда надо решать задачи безопасности и эффективности программирования. Паскалевский код "обрастает" лесенками try...except, try...finally и множественными преобразованиями типов. И эти лестницы становиться все труднее читать по сравнению с С++, где этого можно избежать повесив это на совесть компилятора.

Кстати о преобразовании типов. Делфи обязан безопасному преобразованию типов(as и is) C++, а точнее шаблону dynamic_cast. В продолжении о преобразовании типов. Решая вопрос совместимости, в С++ пришлось оставить процедурные преобразователи - богатые источники ошибок. Но мне было приятно обнаружить более сильные ограничения на нетипизированный указатель в С++, чем в Делфи. Т.к. в С++ указатель и ссылка - это две разные вещи, а в Паскале они перемешаны, то присваивание переменной типа POINTER почти не имеет ограничений. В С++ же они есть. Например, указателю на функцию нельзя присвоить нетипизированный указатель - требуется явное преобразование типов. Но это имеет значение только в системном программировании, куда Делфи ни ногой. Так что не будем зря нападать. Хотя о преобразовании типов я бы поговорил отдельно. Это тема очень сильно влияющая на безопасность и эффективность кода. Например:

procedure TForm.<событие типа TNotifyEvent>(Sender : TObject);

изумительная ловушка для "процедурников". Сразу появляется код типа:
if(Sender is TDBGrid) then (Sender as TDbrid).Tag := 1;
if(Sender is TMenuItem) then
ну и т.д и т.п. Опять леса кода. Народ напрочь забывает о виртуальных функциях.

1) Полагаясь на автоматическую генерацию обработчиков событий вы уменьшаете сопротивляемость вашей проги к вскрытию и анализу. Это порой актуально.
2) Вы строите ужасный и малоэффективный код. Куча if-ов или здоровенный case, в то время как можно обойтись одной-двумя сроками. И сделать вашу прогу более "правильной".

Здесь лучше воспользоваться "подставным" классом с использованием множественного наследования. Правда в Делфях такое же ограничение на множественное наследование, как и в Яве. Один класс должен быть интерфейсным. Но в С++ возможно другое решение. Вы как разработчик знаете, кто может послать то или иное событие? Нет? Так узнайте - это хорошее подспорье к изучении иерархии VCL. Это особенно легко, когда у вас купленная Делфи или Буилдер - вам в коробке дают Большой блестючий лист цветастой иерархии всего славного VCL. Короче, вам лучше знать, что генерит событие, или хотя бы те классы которые вам нужны. Воспользовавшись иерархией, если надо создайте "подставной" класс наследующий private-но родителей тех классов, которые генерят событие. А в секции public опишите все, что нужно открыть для обработчика событий. И опишите свою процедурку с переменной этого класса, а потом можете сами присвоить ее на нужный OnEvent или если не знаете как преобразовывать процедурные типы - просто вызывайте ваш обработчик из стандартного. Попробуйте - это лучше леса if-ов.

Но хватит о преобразовании типов. Давайте перейдем к вашему любимому разделу - недостатки С++ в плане безопасности программирования.
  • 1) Процедурные преобразования типов (будут убраны в сл.стандарте С++)
  • 2) Массивы, как динамические, так и статические. (скорее всего будут убраны)
  • 3) Не вложенные(глобальные) типы указателей и ссылок
  • 4) struct(аналог record в Паскале) и соответственно по умолчанию неявные конструкторы.
  • 5) Хотелось: Что-то напоминающее property, то что есть сейчас - это не стандарт C++.
  • 6) Хотелось: Более гибкая и обширная STL.
  • 7) Хотелось: Стандартизация вызовов процедур.
  • 8) Хотелось: Стандартный Lock для многопоточности.
  • 9) Хотелось: Локальных процедур.
Подробнее. По пункту 1. Здесь нет особой нужды распинаться эту проблему знают все, кто работал над проектом в группе. Т.е. когда встречаешься с такими "опасными" процедурными преобразованиями, то, возможно, это неправильная разработка иерархии классов. Виртуальные функции и динамическое преобразование были созданы, чтобы ими пользовались, а не восхищались.

По пункту 2 и 3. Это напрямую касается Теории Итераторов. Имеется ввиду замена массивов более безопасными контейнерами. Но тогда надо пересмотреть подход к указателям и ссылкам. Т.е. есть такая идея, что программер может ошибиться (не ошибается только тот, кто ничего не делает). Поэтому указатели в его программе могут зайти не туда, куда нужно народу. Говоря по научному - когда указатели находятся вне структур(массивов, классов и т.д.), то ограничения на движения и функции этих указателей лежат целиком и полностью на совести того, кто их ввел. Убирать указатели это не выход (хотя коммерчески - это денежный ход). Это похоже на заточение дома из-за боязни заразиться или умереть. Принцип обобщенного программирования, связанный с операциями над массивами, указателями и ссылками, продемонстрирован на примере STL. Где ИТЕРАТОРЫ - это УМНЫЕ УКАЗАТЕЛИ, а АЛЛОКАТОРЫ - это не менее УМНЫЕ ССЫЛКИ. Одиночные указатели, т.е. те которые указывают на что-то одинокое, например, на динамически созданный класс, реализованы на примере auto_ptr. Они создают экземпляр класса и строго следят за кознями программиста, стремящегося "создать мусор". Итератор - указатель, который может двигаться, описан внутри контейнера и двигаться только в нем и по его правилам. Аллокатор - это ссылка, которая знает, что нужно контейнеру и где. Если бы язык полностью перешел на такую концепцию указателей и ссылок - очень многое изменилось бы в технологии разработки ПО. Судя по последним интервью, Страуструп намеревается именно так и поступить.

По пункту 4. Весьма специфическая проблема, связанная с концепцией ООП в С++. Просто struct это класс, у которого все члены и методы находятся по умолчанию в секции PUBLIC. И отсюда же следует создание неявных копирующих конструкторов. Это и некоторые другие вещи, связанные с этим, губят код начинающих программистов на С++. Убрав тяжелое наследие С++, можно еще выше поднять безопасность генерируемого кода.

По пункту 5. Почти во Всех виндовых компиляторах С++ введен тип PROPERTY. Но это инициатива издателей. На самом же деле все просто - для некоторого участка класса задается доступ. В С++ это легко делать до определенных границ. Т.к. описание PROPERTY - это не что иное, как переопределение операторов(присвоение, разыменование, квадратные скобки), то в С++ можно написать шаблон на эту тему. Только он будет хуже, чем PROPERTY. И вот почему. В этом стандарте С++ переопределение "operator []" возможно, но с ограничениями. Вернее с ограничением. Этот ваш новый оператор "квадратные скобки" может возвращать что-угодно, взяв индекс(аргумент) любого типа, НО ИНДЕКС ДОЛЖЕН БЫТЬ ОДИН. Т.е такие вещи возможны:

          v[10] = 0;
          v['abba'] += t[ Now() ];
          TComplex c;
          v[c] = v[c+1];

Здесь всегда используется один аргумент оператора "[]". А вот как вам придется переопределять многомерный массив:

          v[ TCursor(i,j,'blabla', Now()) ] = 100;

Т.е. такой оператор ведет себя не как встроенный, что не позволяет достаточно изящно определить шаблон PROPERTY. Почему этого не сделано? Ответ чуть ниже, в итогах статьи.

По пункту 6. Здесь все ясно, как днем. Расширение стандартных шаблонов и алгоритмов. Поддержка GUI, расширение потоков до сетевых ресурсов и мультимедиа. Ну и т.д.

По пункту 7. Используя возможности С++, каждый создает свой вариант вызовов процедур. Я не говорю, чтобы свести все к одному. Но стандартизировать парочку-тройку стоит.

По пункту 8. Ну вообще-то это можно включить в STL. В последней (4.0) нечто вроде этого есть, но закрыто. Поэтому приходится писать свои прибамбасы для "залочивания" областей памяти. И делать это приходиться на асме, что не способствует портированию кода.

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

После всего, что я тут наговорил, может возникнуть мысль: "А почему же тогда Борланд двигает Делфи?". "И почему VCL написан на паскале, а не на С++?". Резонно. Мыслям вообще свойственно появляться в головах человеков. Помниться в детстве я очень много играл в футбол, болел за Динамо(Киев) и т.д. Все это в прошлом, но тогда я был очень удивлен, когда во время своей фанатичной увлеченности футболом посмотрел баскетбольный матч NBA. И вы знаете, что мне первое бросилось в глаза? Незаметность судей. Ну не в прямом смысле этого слова. Но по футболу не проходило ни одного матча, чтобы судья не нагадил той или иной команде. Это аналогия. Паскаль маленький язык и это не недостаток. Его не замечаешь, когда пишешь прогу большую или маленькую. Почему? Потому что Паскаль от Борланд специальный язык, т.е. предназначен для узкой области. Узкая - это не значит, что программ мало, просто цели в этой области отличаются не намного. Поэтому резонно было бы выбрать язык, который необходим только для склеивания компонентов или их написания. За все остальное отвечает среда. VCL не является языковым расширением Паскаля - это "ОО" библиотека. Транспортом же между такими библиотеками и отдельными компонентами выступает некая переделка СОМ от Борланд. И опять же отбросьте этот транспорт, который не является частью языка, и от Делфи ничего не останется. Поэтому Делфи очень гармоничная со своими недостатками среда для разработки GUI приложений под винды. И хоть в Делфях можно писать большие приложения, все же она уступает во многом своим современникам в плане безопасности разработки. Возьмите Оберон, Яву, Эйфель(Eiffel) или же С++ все они выгодно отличаются от Делфей.

Говорю я это не для того, чтобы обесценить "Делфу". Это хороший продукт, которому я благодарен за излечение моей боязни виндового GUI. OWL и MFC наоборот способствовали этому. Делфи хорошая ступенька на пути вверх. Но профессиональный программер должен знать несколько языков, как минимум два(не считая асма и SQL). Он должен быть вне своих предпочтений языку, чтобы уметь выбирать наиболее безопасное решение. Рынок программных продуктов - это самый сумасшедший рынок на Земле. Есть самые вредные и опасные рынки. Но software - это просто каша какая-то. Когда язык или программа становятся предметом коммерческих разработок, то увы, но качество продукта не имеет значение. Важно время жизни продукта. Под этим периодом я понимаю, время когда издатель должен ПОДДЕРЖИВАТЬ свой продукт на плаву за счет покупателей. Такие известные монополисты как Microsoft, Oracle да и фирмы по мельче имеют более половины дохода именно за счет СОПРОВОЖДЕНИЯ своих продуктов. Поэтому им и нафиг не нужен OpenSource. Парни, это десятки миллиардов долларов в год! Это очень жирный куш, чтобы с ним так просто расстаться. Это не значит, что мы пали жертвой масонского заговора. Не надо преувеличивать, просто критерий сейчас один - количество денег. Даже этот удивительный язык С++! Казалось, его стандартизировала некоммерческая организация. Да это так. Но ни я, ни вы не видели протоколов этих обсуждений. Да и не надо. Простой факт: STL был принят в стандарт сразу и безоговорочно! Почему? Пришел Страуструп и сказал так надо. И 22 члена комитета проголосовали за. Ни че не напоминает? Другой факт: Почему в тот же STL не были включены возможности, которые необходимы многим девелоперам и которые хотели туда включить авторы STL(Степанов и Ли)? Ответ: пришел Страуструп и сказал - хватит! Остановимся! Сами думайте зачем это ему надо было.

У многих людей произошла перестановка качеств "БЫТЬ" и "ИМЕТЬ". Люди думают, что важнее "ИМЕТЬ". Что если они будут "ИМЕТЬ", то они смогут и "БЫТЬ". Но это не так. Это приятная и незаметная ловушка. Все как раз с точностью до наоборот. Еще несколько фактов из раздела Money Oriented Programming:

Еще факт 1: (из интервью Алекса Степанова)

Q: Java is a very new language, still it lacks templates, so it prevents using Generic Programming. Everything must be a class. What do you think of Java?

A: I spent several months programming in Java. Contrary to its authors prediction, it did not grow on me. I did not find any new insights - for the first time in my life programming in a new language did not bring me new insights. It keeps all the stuff that I never use in C++ - inheritance, virtuals - OO gook - and removes the stuff that I find useful. It might be successful - after all, MS DOS was - and it might be a profitable thing for all your readers to learn Java, but it has no intellectual value whatsoever. Look at their implementation of hash tables. Look at the sorting routines that come with their "cool" sorting applet. Try to use AWT. The best way to judge a language is to look at the code written by its proponents. "Radix enim omnium malorum est cupiditas" - and Java is clearly an example of a money oriented programming (MOP). As the chief proponent of Java at SGI told me: "Alex, you have to go where the money is." But I do not particularly want to go where the money is - it usually does not smell nice there.
//////////////////// Без Комментариев!

Еще факт 2: С# от Микрософт. /// Без Комментариев! Язык будет распространяться, но благодаря рекламе и миллиардам МС. Я прочитал как-то статью про Си-шарп от какого-то знатного СИониста от МС. Я очень долго смеялся. Есть такой термин "гнилой наезд". Короче это не тема данной статьи.

Еще факт 3: Линукс. Так, линуксоиды по тормозам. Начнем с того, что я считают Юникс более сильной ОС для сетевой рабочей станции. И тот же разрекламированный Линух имеет ряд преимуществ по сравнению с виндос НТ или 2К. Но почему он так распространяется?
  • а) интернет. провайдеры и те кто идут в электронную коммерцию
  • б) дешево(условно бесплатно)
  • в) удачные надежные утилиты GNU, да еще Sabma с Апачем. Ну вы меня понимаете?
  • г) "Враг моего врага - мой друг".
МС ведет себя на рынке, как слон в посудной лавке. Достается всем. Достается хорошо! Поэтому крупные корпорации и поддерживают Линукс. Проходит время и принцип условно-бесплатного распространения претерпевает несколько изменений в худшую сторону. Настолько, что даже Торвальдс страшно этим недоволен. Идет мощная реклама Линукса. Журналюги, чуя легкую, наживу слетелись на эту тему и строчат одну статью за другой. Ядро же Линуха меняется, но не в сторону разработчиков, а в сторону пользователей. Если это будет продолжаться в том же духе, то мы с вами будем иметь повтор истории MS-DOS, Win 3.х, win 9х. Обидно и не хочется. Надеюсь, что все будет не так. Но пока Линух остается хорошим примером Money Oriented Programming.

Остановимся. Буду закачивать. Итог прост. Money Oriented Programming(МОР) и безопасное программирование вещи взаимно исключающие друг друга. С++ в меньшей мере стало жертвой МОР, чем, например, Делфи. Но вырисовывается неприятная тенденция. Создаются новые стандарты СИ (С99), полностью не совместимые с С++. Создаются коммерческие разновидности С++, которые имеют отличия от стандарта. Пока не все так плохо. Но возможно из-за этой тенденции нам придется ждать стандарта С++ либо очень долго(лет 10. Об этом кстати заявляет Страуструп), либо его вообще не будет. Получается так, что планку, которую установил С++ в безопасном программировании, смогут перепрыгнуть лет через пять некоторые МОР-языки. С одной стороны это хорошо. Это повлияет на рост качества ПО. Но на самом деле же получится насильственное торможение. Имея С++, уже сейчас можно писать безопасные программы на том уровне, который будет доступен другим только через пару лет. Я не привожу другие не менее безопасные языки (Эйфель например), потому что С++ более распространен(много платформ и сред разработки) и имеет больше шансов выжить. Но отказ создавать более безопасный язык, концепции которого уже можно сказать обкатанными на том же С++ или других ООП языках, так вот, отказ от этого - это обыкновенное зазнайство. В этом плане нестандартные и непортируемые МОР-языки имеют преимущество. Издатели меняют их когда хотят и как хотят. Мне же хотелось бы увидеть через годик хотя бы черновой стандарт нового языка от С++.

Вторая часть "ИТОГО". Очень важно как поведут себя коммерческие и некоммерческие языки в своей войне за девелоперов. Но не менее важно другое. Кто победит "КОРОБОЧНЫЙ" софт или OpenSource. Это тоже имеет прямое отношение к безопасному программированию. Даже больше, чем безопасность самого языка. Каким бы чудным языком вы не владеете, в случае с закрытым API, вы зависите от качества разработки этого API. Тут примеров приводить не надо, и так все понятно. В случае же с OpenSource вы можете влиять на весь программный слой. В случае преобладания КОРОБОК, происходит ЦЕНТРАЛИЗАЦИЯ разработок. Сами разработчики или же их разработки кучкуются возле областей, которых можно перечесть по пальцам. Отсюда и гигантские размеры современных фирм разработчиков. Если появляется махонькая конторка, которая выдает что-то выдающееся, - она тут же попадет в зависимость от какого-нибудь монстра. Ее либо покупают, либо заключают кросс лицензионное соглашение. Все эти неясности в суде связаны именно с тем, что машинный код невозможно лицензировать. Это как в литературе. Если ты лицензируешь жанр, например детектив(аналогия с алгоритмом), то ты монополист с точки зрения суда. Если же ты лицензируешь только свой текст, то остальные "авторы" лишь поменяют имена героев и будут еще тебя обвинять в воровстве идей. В случае же OpenSource такого произойти не может. Более того, т.к. пропадает такая штука, как СОПРОВОЖДЕНИЕ, то и пропадает ЦЕНТРАЛИЗАЦИЯ РАЗРАБОТОК. Программисты "расползутся", "растекутся" по фирмам, у которых возникнет неожиданная потребность в таких кадрах. Ведь теперь сопровождением может заниматься сама фирма. Монополии тогда будет очень сложно возникнуть. Лидеры несомненно будут. Но пропадут "программерские провинции", какой на мой взгляд является Россия. Наши девелоперы, как юзеры - пользуются тем, что выходит на Западе. И большие бобы в таком плане разработчикам не светят, только если они не откроют новую сферу рынка. Если же победит OpenSource, то, благодаря Инету или тому что его заменит, фирмы поддерживающие продукт(т.е. любая группа программеров) могут иметь законные бабки, которые они конечно делят с авторами программы. Такой рынок будет способствовать выделению именно настоящего безопасного языка, а не разрекламированного. И мне, как профессиональному программисту, до лампочки что это будет TurboEiffel, Pascal++, Oberon--, или С2000. Потому что это будет на тот момент действительно безопасный язык, который использует большинство профессионалов. Потому что тогда будет выгодней создавать СВЕРХ КАЧЕСТВЕННЫЙ продукт, чтобы не делиться не с кем деньгами.

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

Все. Пока.

9 сентября 2000г.
Логинов Дмитрий.

Обязательное PS:

Совсем забыл! В этой статье в обзоре не учавствует С#. Почему? Это также классический МОР-язык он отличается от Явы большим удобством и возможностями из-за близости к С++. Почему же он МОР-язык? Действительно пусть его придумала МС и отдала его ECMA (Европейская Ассоциация Производителей Компьютеров). НО коммерческий задел МС себе оставила. Дело все в том, что:
  • 1) Эпоха ПК подходит к концу.
  • 2) Преобладаение одной платформы заканчивается
  • 3) Суд над МС.
Один раз МС лажанулась с инетом. Потеряла большие деньги и до сих пор агрессивно работает локтями, чтобы в этом рынке поиметь тоже, что и на рынке ПК. Ей нужен билет "в счастливое будущее". Дело все в том, что если продажи Windows платформ уйдут в небытие. То МС привратиться в "рядового гиганта" компьютерной индустрии. А если ее еще и разделят... Короче, ей срочно нужна "СМЕНА МОНОПОЛИИ". МС придумывает NET. Т.к. это только концепт, то весь он и его продукты не учавствовали в моей статье. То что рекламируется и то что будет - это две большие разницы. NET - это мысль, которая давно и неоднократно приходила в голову тем или иным умникам. Некая ее проекция наблюдается на примере "Developer Office" от Борланд. Минимизирую аналогию. Библиотека компанентов в Борландовских продуктах не так уж "языко-независимая", но они постарались это сделать. Вы запускаете Builder и начинаете вести проект. Один модуль у вас может быть на Делфях, другой на С++, для язвращенцев можно даже родить модуль на асме. Все упирается в одно большое слово КОМПАНЕНТЫ. В концепции NET все упирается в КЛАССЫ. Хотя какая разница? Вы пишите модуль или один класс на любом предоставленном МС языке: С++, С#, VB, JavaScript, Visual FoxPro etc. А их компиляторы генерят классы для стековой машины (аналог явовской KVM) на промежуточном языке IL(intermediate language). Вот и вся фича. Если сюда прилопатить Оффис, SQL и Exchange сервера, то у юзера не будет другого выбора, кроме как покупать все скопом. Поэтому-то MS с легкой совестью "подарил" свою "новинку", т.к. С-диез без концепции NET пустой звук. Тем более, что NET продукты будут выпускаться в течении года только для windows платформ! Чем для винды не билет в будущее. Просто тянут за уши!

Поэтому-то я не рассматривал "несуществующий"(хотя сырцами уже меняются на конфах) язык в плане безопасности программирования. Не смотря на то, что С-диез заметно превосходит Яву, победит некто третий. Он подождет, подождет когда УСТОИТСЯ вся эта шумиха. Посмотрит какой выбор сделали девелоперы и выпустит новый язык, совместимый с наиболее распространенным и назовет его...

Дмитрий Логинов
Специально для Королевства Delphi




Смотрите также материалы по темам:
[Средства разработки. Языки программирования.]

 Обсуждение материала [ 22-12-2008 10:56 ] 4 сообщения
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования