Достоинства языка и его критика 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Достоинства языка и его критика



Особенности

С одной стороны, С++ является потомком Симулы, которую Алан Кэй определил[14] как «Алгол с классами», и потому будет актуальной оценка С++ в сравнении с другими языками из семейства потомков Алгола (Basic, Pascal, Java, C#, Visual Basic, Delphi, D, Oberon и пр.). С другой стороны, С++ претендует на мультипарадигменность и универсальную применимость (в отличие от Си, ориентированного на очень узкий круг задач), и используется в промышленности намного шире других потомков Алгола, и потому будет актуальной оценка С++ в сравнении со всем многообразием применяемых языков, включая и Си. Во избежание повторений, оценки обычно совмещаются.

С++ — язык, складывающийся эволюционно. В отличие от языков с формальным определением семантики (см. Спецификация языков программирования), каждый элемент С++ заимствовался из других языков отдельно и независимо от остальных элементов (ничто из предложенного С++ за всю историю его развития не было новшеством в Computer Science), что сделало язык чрезвычайно сложным, со множеством дублирующихся и взаимно противоречивых элементов, блоки которых основаны на разных формальных базах. В этом отношении С++ повторяет путь PL/1, но, в отличие от последнего, длительное повсеместное использование С++ обеспечил выбор языка Си в качестве отправной точки.

Критики С++ не противопоставляют ему какой-либо конкретный язык, а наоборот, утверждают, что для всякого случая применения С++ всегда существует альтернативный инструментарий, позволяющий решить ту же задачу более эффективно и качественно. В свою очередь, сторонники С++ считают некорректным сравнивать различные аспекты С++ с совершенно различными языками, так как общий набор средств и возможностей С++ существенно шире, чем в большинстве языков, с которыми проводится сравнение, и сама по себе широта возможностей, на их взгляд, является веским оправданием несовершенства каждой отдельно взятой возможности. Более того, по их мнению, высокая совместимость с Си является одной из принципиальных черт языка, и потому все недостатки С++ оправданы преимуществами, предоставляемыми этой совместимостью (см. раздел Философия C++). При этом сторонники C++ игнорируют подтверждённый исследованиями[15][16] факт, что декомпозиция проекта на несколько разных языков, наиболее пригодных для своих мини-задач, (или просто использование одного наилучшим образом подходящего языка) сокращает на порядок общую трудоёмкость разработки при одновременном повышении на порядки основных показателей качества программирования. По этой причине критики не соглашаются рассматривать недостатки С++ по отдельности и тем более делать поправку на «универсальность», утверждая, что если задача требует одновременно высокоуровневых и низкоуровневых возможностей, то использование Си совместно с языками, из которых заимствованы отсутствующие в Си возможности C++, будет более разумным, чем внедрение этих возможностей в сам Си и использование полученного языка резко возросшей степени внутренней сложности.

Таким образом, одно и то же свойство языка (совмещение большого числа элементов разных языков и отсутствие конкретной целевой ниши применения) рассматривается сторонниками как «главное достоинство», а критиками — как «главный недостаток».

Следует отметить, что рассмотрение элементов языка по отдельности означает отказ от рассмотрения языка как системы, а следовательно, и от ожидания синергизма,— что делает ограниченным (конечным) потенциал роста сложности абстракций, которые можно выразить посредством этого языка, и соответственно сужает спектр реализуемых на нём программных систем.

С++ заявляется как кроссплатформенный: стандарт языка накладывает минимальные требования на ЭВМ для запуска скомпилированных программ. На практике, для написания портируемого кода на С++ требуется огромное мастерство и опыт, и «небрежные» коды на С++ с высокой вероятностью могут оказаться непортируемыми[17]. Тонкое владение С++ в принципе может сделать код на С++ столь же портируемым, что и код на Си (хотя, по мнению Линуса Торвальдса, С++ при этом фактически сократится до своего подмножества Си[мнения 3]). Однако, критики С++ утверждают, что изучение и использование одновременно всех языков, противопоставляемых С++ (не вызывающих серьёзных проблем при портировании), в сумме требует примерно тех же интеллекта, усилий и временных затрат, что и изучение и использование одного только С++ на высококлассном уровне — в связи с чем становится актуальной также оценка порога вхождения и результативности (производительности и качества труда программистов).

Многие аспекты в спорах «за и против С++» обусловлены расхождением в сущностном понимании процесса стандартизации. Б.Страуструп и его последователи считают, что «стандарт — это контракт между программистами, разрабатывающими программы на языке, и программистами, разрабатывающими компиляторы языка»[7]. Каждый новый стандарт С++ являлся декларацией того, что отныне должно быть реализовано во всех компиляторах — при том, что С++ имеет естественное определение семантики, то есть потенциально зависим от реализации (стандарт содержит множество пунктов, определённых как «implementation-defined»). Традиционно, успешная стандартизация в технике представляет собой формальный перевод стандарта из статуса де-факто в статус де-юре (подытоживание общепринятых, устоявшихся знаний для обеспечения надёжной внутриотраслевой совместимости). Все противопоставляемые С++ языки программирования, если проходили процедуру стандартизации (что не обязательно для портируемости), то лишь после многолетней апробации на практике; при этом все они изначально имеют формальное определение семантики, так что накапливающиеся к моменту стандартизации изменения от первой версии языка оказываются не принципиальны. Теоретически эти определения стандартизации тождественны. На практике определение Страуструпа согласуется с традиционным лишь при условии, что нет абсолютно никаких препятствий против немедленного и беспрекословного подчинения статуса де-факто декларированному де-юре — то есть если абсолютно все компиляторы реализуют поддержку нового стандарта сразу после его выхода и со 100 % соответствием ему, и что новый стандарт не отменяет никаких положений старого, кроме признанных убыточными или вредоносными. Так может происходить лишь в условиях полного отсутствия человеческого фактора (иначе говоря, при отсутствии программистов как таковых). На практике именно этим и обусловлено значительное отставание реальной кроссплатформенности С++ от заявленной разработчиками стандарта и противоречивость предлагаемых возможностей. Сторонники С++ считают взгляд Страуструпа на стандартизацию более практико-ориентированным и принимают порождаемые им проблемы за должное.

Достоинства

C++ содержит средства разработки программ контролируемой эффективности для широкого спектра задач, от низкоуровневых утилит и драйверов до весьма сложных программных комплексов. В частности:

  • Высокая совместимость с языком Си: код на Си может быть с минимальными переделками скомпилирован компилятором C++. Внешнеязыковой интерфейс является прозрачным, так что библиотеки на Си могут вызываться из C++ без дополнительных затрат, и более того — при определённых ограничениях код на С++ может экспортироваться внешне не отличимо от кода на Си (конструкция extern "C").
  • Как следствие предыдущего пункта — вычислительная производительность. Язык спроектирован так, чтобы дать программисту максимальный контроль над всеми аспектами структуры и порядка исполнения программы. Один из базовых принципов С++ — «не платишь за то, что не используешь» (см. Философия C++) — то есть ни одна из языковых возможностей, приводящая к дополнительным накладным расходам, не является обязательной для использования. Имеется возможность работы с памятью на низком уровне.
  • Поддержка различных стилей программирования: традиционное императивное программирование (структурное, объектно-ориентированное), обобщённое программирование, функциональное программирование, порождающее метапрограммирование.
  • Автоматический вызов деструкторов объектов в адекватном порядке (обратном вызову конструкторов) упрощает и повышает надёжность управления памятью и другими ресурсами (открытыми файлами, сетевыми соединениями, соединениями с базами данных и т.п.).
  • Перегрузка операторов позволяет кратко и ёмко записывать выражения над пользовательскими типами в естественной алгебраической форме.
  • Имеется возможность управления константностью объектов (модификаторы const, mutable, volatile). Использование константных объектов повышает надёжность и служит подсказкой для оптимизации. Перегрузка функций-членов по признаку константности позволяет определять выбор метода в зависимости цели вызова (константный для чтения, неконстантный для изменения). Объявление mutable позволяет сохранять логическую константность при виде извне кода, использующего кэши и ленивые вычисления.
  • Шаблоны C++ дают возможность построения обобщённых контейнеров и алгоритмов для разных типов данных. Попутно шаблоны дают возможность производить вычисления на этапе компиляции.
  • Возможность расширения языка для поддержки парадигм, которые не поддерживаются компиляторами напрямую. Например, библиотека Boost.Bind позволяет связывать аргументы функций. Используя шаблоны и множественное наследование, можно имитировать классы-примеси и комбинаторную параметризацию библиотек. Такой подход применён в библиотеке Loki, класс SmartPtr которой позволяет, управляя всего несколькими параметрами времени компиляции, сгенерировать около 300 видов «умных указателей» для управления ресурсами.
  • Возможность встраивания предметно-ориентированных языков программирования в основной код. Такой подход использует, например библиотека Boost.Spirit, позволяющая задавать EBNF-грамматику парсеров прямо в коде C++.
  • Доступность. Для С++ существует огромное количество учебной литературы, переведённой на всевозможные языки. Язык имеет низкий порог вхождения, но среди всех языков такого рода обладает наиболее широкими возможностями.

Критика

Сторонники С++ позиционируют его как «универсально применимый» — вплоть до отождествления «применимости» с Тьюринг-полнотой (что является ошибкой) и одновременно с оптимальностью, то есть обоснованностью выбора его в качестве инструмента для данной конкретной задачи; при этом ни одной конкретной задачи не обозначается, а наоборот, делается утверждение, что С++ подходит для любой задачи (что теоретически невозможно[пояснения 2]). Однако, С++ не отвечает многим требованиям качества программирования, не предъявляемым к Си, но важным для широкого спектра задач прикладного программирования. В частности, критики полагают, что:

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

В то же время, критике подвергается и применимость С++ в низкоуровневой разработке в качестве «улучшенного Си».

Многие конкретные недостатки вытекают непосредственно из свойств семантики системы типов языка: она не отвечает требованиям полноты и ортогональности, при этом обладает избыточностью и предусматривает понятие «приведения типов» (как явно, так и неявно). В отношении типизации, С++ чаще всего противопоставляются либо типизируемые по Хиндли-Милнеру, либо динамически типизируемые языки. Начиная со стандарта C++0x, в языке появилась возможность автоматического выведения типов, из-за чего возникло заблуждение, что отныне «С++ поддерживает вывод типов по Хиндли-Милнеру». Однако, системе типов С++ противопоставляется не сам механизм выведения типов, а полиморфная семантика системы типов Хиндли-Милнера, предусматривающая в том числе и механизм выведения, но не как главное преимущество. Существуют примеры развития Си по пути типизации Хиндли-Милнера (см. раздел Влияние и альтернативы).

Критика C++ с позиций только ООП (без сравнения методологий проектирования) с описанием вреда от влияния C++ на другие языки приведена в работе[18]. В случае языково-ориентированного проектирования программ применимость С++, как и при использовании любых других языков, ограничивается нижним уровнем системы — реализацией предметно-ориентированных языков (DSL) первого уровня. Для этой задачи С++ объективно является далеко не оптимальным выбором (см. раздел Отсутствие возможностей). Более того, в случае реализации DSL без трансляции, поверх основного языка (что является традиционным для Lisp/ML, и для чего предназначена библиотека Boost.Spirit в С++), с т.з. воплощения изоморфизма Карри-Ховарда выбор С++ в качестве базы был бы абсурден (см. раздел Избыточные и опасные возможности).

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

Синтаксис

Громоздкость

Определение синтаксиса является одним из самых громоздких (стандарт 2003 г. содержал уже более 200 строк РБНФ, и язык продолжает расширяться), к тому же содержит неоднозначности (disambiguations), что порождает многие проблемы:

  • крайне затрудняется автоматическая работа с исходными текстами на языке — даже задача написания компилятора C++ оказывается на редкость трудоёмкой, а статический анализ или эквивалентное преобразование программ оказываются вообще ограниченными по возможностям. [19]
  • становится невозможным редактирование синтаксиса языка программистом для адаптации к требованиям предметной области (как это возможно, например, в OCaml посредством управления модулем camlpX компилятора, или в метаязыках посредством синтаксических макросов).
  • практически гарантированы (предсказуемы с очень высокой вероятностью в силу человеческого фактора) отклонения от стандарта в различных компиляторах, что обеспечивает низкую портируемость программ на C++ между компиляторами (см. раздел Особенности). Большинство промышленных языков (даже не стандартизированных) защищено от этого формальной семантикой.

Избыточность

В С++ одни и те же (или близкие) возможности дублируются группами элементов, части которых несовместимы между собой. Например, управление памятью осуществляется четырьмя способами: различаются память «на стеке» и «на куче», и для последней, наряду с унаследованными из Си операциями malloc/free, добавляются пары new/delete и new[]/delete[]. При этом не осуществляется никакого контроля за парностью, т.е. можно выделить блок с помощью new[], а затем высвободить с помощью delete или даже free. Это приводит к скрытым ошибкам и нестабильной работе программ (т.к. new и new[] реализованы через malloc, но выполняют больше инициализаций, подчистка которых в этом случае не производится), и особенно чреваты этим места возможной генерации исключений (объекты на стеке автоматически деинициализируются, на куче — нет, если не использованы RAII-обертки). То же касается приведения типов — наряду с наследованным из Си простым приведением типов, в С++ добавлены специальные операции — dynamic_cast, static_cast, const_cast, reinterpret_cast — и нет никаких языковых ограничений на перемешивание их в тексте со старыми.

Ограниченность возможностей

Рефлексивное метапрограммирование

Рефлексивное метапрограммирование в С++ невозможно (равно как и во всех остальных потомках Алгола). Интроспекция предусмотрена, но реализована отдельно от основной системы типов, что делает её практически бесполезной. Наибольшее, что можно получить — параметризацию поведения на заранее известном диапазоне случаев. Это является препятствием против применения С++ в большинстве подходов к реализации Искусственного Интеллекта (за исключением самых примитивных, реализующих статический алгоритм).

Порождающее метапрограммирование

Порождающее метапрограммирование на основе шаблонов C++ трудоёмко и ограничено по возможностям. Оно осуществляется за счёт реализации статического (исполняемого на этапе компиляции) интерпретатора примитивного функционального языка программирования посредством шаблонов C++, а также примитивного препроцессора, унаследованного от Си. Встраиваемые предметно-ориентированные языки, реализованнные техникой expression templates, требуют знания самого С++ для их использования, т.к. возводимые барьеры абстракции сохраняют все свойства нижележащей реализации, что не обеспечивает полноценное разделение труда. Таким образом, возможности С++ по расширению возможностей самого С++ весьма ограничены. Перечисленных недостатков лишены метаязыки, такие как диалекты Lisp и ML, их потомки и гибриды (Haskell, Nemerle), а также Рефал, и потому они являются оптимальным выбором для задачи разработки языков тем или иным способом. В Common Lisp, Nemerle и Template Haskell имеются подсистемы метапрограммирования, делающие доступным свободное определение синтаксиса операций; с их помощью становится возможным встраивание в код предметно-ориентированных языков, не требующих знания основного языка, что обеспечивает эффективное разделение труда разработчиков[20][21]; подобные возможности иным способом предоставляют SML и OCaml. Причиной отставания мощности языка шаблонов С++ является то, что, в отличие от метаязыков, где в качестве подсистемы метапрограммирования используется сам основной язык, в С++ язык шаблонов представляет собой отдельный язык, не совпадающий и не пересекающийся с самим С++ (который к тому же не был задуман создателями языка, а был спонтанно обнаружен хакерами), из-за чего потенциал роста сложности абстракций оказывается ограниченным. В языке D также реализована подсистема шаблонного метапрограммирования, сравнимая по мощности с оной в С++, но более простая в применении.

Функциональное программирование

Явная поддержка функционального программирования присутствует только в стандарте C++0x, вышедшим лишь после почти 30 лет развития языка. До этого данный пробел устранялся различными библиотеками (Loki, Boost), использующими язык шаблонов для расширения основного языка функциональными конструкциями. Качество подобных решений значительно уступает качеству встроенных в функциональные языки решений[пояснения 3] и качеству реализаций высокоуровневых возможностей С++ (таких как ООП) посредством функциональных языков. Все реализованные в C++ возможности ФП оказываются лишь их эмуляцией и используются совместно с императивными возможностями, что не даёт возможности применения присущих ФП мощных оптимизационных методик (см. раздел Вычислительная производительность). Кроме того, из-за трудоёмкости использования шаблонов, на практике ФП в С++ обычно ограничивается вызовами функциональных библиотек и реализацией отдельных методов, и практически не даёт преимуществ в проектировании программ (см. Соответствие Карри — Ховарда и пред.пункт), так что оно в С++ по-прежнему осуществляется обычно лишь посредством объектно-ориентированной декомпозиции.

Контроль за поведением

В С++ широко используется ситуативный (ad hoc) полиморфизма — явное описание различного поведения для разных ситуаций под единым идентификатором — то есть перегрузка функций. Перегрузка операторов призвана дать возможность введения в программу т. н. «синтаксического сахара», но в С++ может поощрять к бесконтрольному изменению поведения элементарных операций, в том числе new/delete и new[]/delete[], для разных типов (что резко повышает риск разного рода ошибок). Это обусловлено тем, что вводить новый синтаксис нельзя (хотя синтаксис стандартных операторов С++ адекватен семантике далеко не всех типов, которые может потребоваться ввести в программу). [пояснения 4] Значительная часть перегруженных функций и операторов вызывается неявно (приведение типов, создание временных экземпляров классов и др.). Попутно идеология языка спутывает «контроль за поведением» с «контролем за эффективностью» — что представляет опасность, так как де-факто возможности явного контроля этих аспектов исполнения программы со стороны человека являются взаимоисключающими[пояснения 5]. В сочетании с изобилием побочных эффектов всё это приводит к тому, что по мере роста сложности системы код на С++ не абстрагируется, а, наоборот, усложняется, и на порядки[ источник? ] снижаются показатели понимаемости и тестируемости — возникает необходимость контролировать (как чтением, так и отладкой) слои реализации по разные стороны от текущего барьера абстракции, что считается плохой практикой в программировании. В результате трудоёмкость (а значит, и стоимость) разработки экспоненциально[ источник? ] растёт от объёма реализованной функциональности (в языках с полиморфной семантикой системы типов этот рост имеет логарифмический порядок).

Абстракция

Система типов обладает крайне слабыми средствами абстракции[ источник? ]. Некоторые отсутствующие в С++ семантические возможности могут реализовываться на уровне библиотек (такие как сборка мусора или длинная арифметика), однако для получения выгоды от них необходима[ источник? ] глубокая модификация имеющегося кода под новый модуль, тогда как в языках самых разных семантик, предоставляющих тот или иной вид полиморфизма в хорошо развитом виде (будь то ML, Smalltalk или даже Python), выгода от нового кода зачастую обеспечивается простой подменой имеющегося модуля на новый[ источник? ].

Модульность

Плохая поддержка модульности. В классическом Си модульность на уровне языка отсутствует, и её обеспечение переложено на компоновщик — С++ унаследовал это свойство. Подключение интерфейса внешнего модуля через препроцессорную вставку заголовочного файла (#include) серьёзно замедляет компиляцию при подключении большого количества модулей (так как результирующий файл, подаваемый на вход парсеру, оказывается очень велик). Для устранения этого недостатка многие компиляторы реализуют механизм прекомпиляции заголовочных файлов.



Поделиться:


Последнее изменение этой страницы: 2016-12-12; просмотров: 365; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.235.140.73 (0.025 с.)