В комментариях к переводу «30 лет С++» было заметно, что далеко не все этапы эволюции языка одинаково хорошо известны, иногда вообще нету представления о происхождении и процессе развития того или иного элемента синтаксиса или соответствующей семантики. Возможно, этой заметкой удастся заинтересовать читателей обратится к уже давно не новой книге автора языка с целью формирования более полной картины о C++. Книга рассказывает как происходило его развитие, что оказывало влияние на этот процесс и почему было отдано предпочтение одним подходам вместо других.
Ниже приводится набор кратких выдержек из книги, представленных в вольной форме и иногда дополненных примерами. Порядок изложения в основном повторяет относительный порядок их появления в книге. Нумерация используется для возможных ссылок на отдельные пункты. Здесь намеренно не будет повторения общеизвестных фактов, так как в этом нету большого смысла. Внимание было уделено как раз менее известным из них и наиболее интересным с точки зрения автора поста. Некоторые пункты являются цитатами, которыми всё сказано, ряд других просто упоминают потенциально интересные или же забавные факты. Также многие детали опущены, их следует искать в книге.
Часть материала несколько пересекается с недавними постами, но не была вырезана для полноты. Порядок может показаться сумбурным, но на то это и выборка, дополнительная причина «беспорядка» обозначена в заключении. При чтении полезно иметь в виду, что первое издание книги вышло в начале 1994 года.
Выдержки
- Всё началось в 1971 году, когда Бъёрну Страуструпу понадобился быстрый язык для выполнения моделирования.
- Классы пришли в первую очередь из Simula.
const
-квалификаторы обязаны своим существованием ROM.- Деннис Ритчи принимал активное участие в обсуждении дизайна C++. (Ниже будет видно, как сильно C и C++ влияли друг на друга.)
- «Изначально в языке предлагались общие механизмы организации программ, а вовсе не поддержка конкретных предметных областей», т.е. мультипарадигменность у C++ врождённая и соответствует задумке его автора.
- «Я искренне убеждён, что не существует единственно правильного способа написать программу, и дизайнер языка не должен заставлять программиста следовать определённому стилю.»
- Первоначальная реализация не поддерживала виртуальные функции.
- Множественное наследование было добавлено спустя годы после начала разработки языка.
- Изначально конструкторы классов назывались
new
и возвращалиvoid
(хотя его можно было опускать). При этом именно конструкторы выделяли память под объект, а деструкторы освобождали её:class stack { void new(); // new(); };
- Исходным именем деструкторов было
delete
и они тоже возвращалиvoid
(хотя и его можно было опускать):class stack { void delete(); // delete(); };
- Вместо двойного двоеточия (
::
) для объявления методов классов использовалась точка:char stack.pop() { // ... }
- Имена классов пребывали в отдельном пространстве имён и должны были предваряться ключевым словом
class
(также как иstruct
/union
в C):class stack stack; class stack *thatStack = &stack;
- Само ключевое слово
class
пришло из Simula, а так как Бьёрн не любитель изобретать терминологию то оставил то, к чему привык. - Язык Simula позволял создавать экземпляры классов только на куче, что было крайне неудобным и подало идею позволить создавать их в C++ на стеке либо глобально.
- «C++ — это просто ещё один язык в системе, а не вся система.»
- В первой реализации не существовало возможности получить доступ к
this
, но это было в большей степени следствием ошибки. - Автопрототипирование (вывод и запоминание прототипа неизвестной функции в месте первого обращения к ней) изначально было придумано для C++, но позже было использовано в C (хотя и объявлено устаревшим сейчас):
function(1, 1.0); // Компилятор запоминает, что function принимает int и double. function("string"); // Компилятор выдаёт ошибку из-за несоответствия типов аргументов int и double.
- Объявление функции, не принимающей аргументы, с использованием
(void)
было добавлено сначала в C++, а только потом в C (тогда в C++ от этой идеи уже отказались и решили использовать пустые круглые скобки):void noArgsInCpp(); void noArgsInCAndCpp(void);
- Объявление неявного
int
в качестве устаревшего было изначально предложено для C++:function() {} // Компилятор предполагает, что function возвращает int.
- В ранние периоды предпринимались попытки запретить сужающие преобразования (
long -> int
,double -> int
), но из-за слишком большой распространённости было решено отказаться от этой идеи:void f(long lng, int i) { i = lng; // Вызвало бы ошибку. char c = i; // Вызвало бы ошибку. }
- Перегрузка операторов, ссылки и возможность объявлять переменные в любом месте блока пришли из ALGOl 68.
- Однострочные комментарии (
//
) — из BCPL. - Источники, использованные при разработке исключений: Ada, Clu, ML.
- Шаблоны и пространства имён позаимствованы из языка Ada.
- Указание типов параметров в объявлении функции — впервые реализовано в C++, позже адаптировано в C.
- Рассматривалась возможность введения альтернативного синтаксиса объявлений (недавно был пост по близкой теме):
// Радикальный вариант: v: [10]->int; // int *v[10]; p: ->[10]int; // int (*p)[10]; // Менее радикальный вариант: int v[10]->; // int *v[10]; int p->[10]; // int (*p)[10]; int f(char)->[10]->(double)->; // int *(*(*f(char))[10])(double);
Новый синтаксис не был проработан во всех деталях и в язык он так и не попал ввиду небольшой значимости изменения на фоне потенциально больших проблем с обратной совместимостью.
- Отказ от обязательных префиксов типов (
struct
/union
/class
) привёл к возможности объявления переменных, совпадающих по имени с классами (по большому счёту ради совместимости с C):class Class { }; int main() { Class Class; return 0; }
- Одно время разрешалось объявление новых составных типов в списке аргументов или же прямо в возвращаемом значении функции:
class A { // ... } get() { return A(); }
- Ранее «вытягивание» членов базовых классов не требовало использования ключевого слова
using
, достаточно было просто указать имя члена:class Base { protected: void doSomething(); void doSomethingElse(); }; class Derived : public Base { public: doSomething; // using doSomething; Base.doSomethingElse; // using doSomethingElse; };
- Изначально, дружественными (
friend
) могли быть только классы. Общей идеей концепции является помещение ряда сущностей в один домен безопасности, члены которого равны между собой в правах (а не нарушение инкапсуляции, как многие считают). - Возможно, именно Страуструп изобрёл концепцию конструктора.
- Изначально, каждый объект мог содержать методы
call()
иreturn()
, которые вызывались соответственно перед и после выполнения любого метода класса. Аналогичные методы:before
и:after
есть в CLOS.class ProtectedAccess : object { call(); // Захватить примитив синхронизации. return(); // Освободить примитив. };
Позже решили, что эта возможность добавляет больше трудностей в язык, чем приносит пользы.
- Несколько раз рассматривалась возможность включения сборщика мусора в язык, но была признана неприемлемой.
- Прямая поддержка многопоточности также рассматривалась, но было решено оставить её для реализации в виде библиотеки.
- Какое-то время язык назывался C84 для избежания путаницы, так как пользователи замещали C with classes чем-то вроде «новый C», «улучшенный C» и т.д. Оптимистично считая, что C будет стандартизирован в 1985, Бьёрна попросили поменять название ещё раз, во избежания возможной неоднозначности со «стандартным C».
- Первый же компилятор C++ (не препроцессоры, которые использовались изначально) был написан на C++ (Cfront, C with classes и C84, вроде, были написаны на C).
- Вероятно, Cfront был первым компилятором неполного цикла, генерирующим код на C. За ним последовали Ada, Eiffel, Lisp, Modula-3, Smalltalk.
- Первая реализация исключений была добавлена Hewlett-Packart в 1992 году.
- Виртуальные функции были заимствованы из Simula, но с модификациями.
- Определение типа во время исполнения первоначально не было добавлено намеренно, чтобы заставить пользователей применять статический контроль типов и виртуальные функции, а не run-time тип, который по своей сути —
switch
. struct
почти эквивалентноclass
с целью единообразия и унификации.- До Cfront 2.0
operator=
мог быть глобальной функцией, позже отказались от этой идеи из-за конфликтующей предопределённой семантики.class C {}; C & operator=(C &rhs) {} // Приводит к ошибке: // src.cpp:3:21: error: ‘C& operator=(C&)’ must be a nonstatic member function
- Определение переменной в месте её использования взято из ALGOL 68.
- Ссылки также заимствованы из ALGOL 68, но там они могли быть переприсвоены после инициализации.
- Перегрузка по lvalue/rvalue рассматривалась ещё во времена Cfront 1.0.
- readonly/writeonly указатели/данные были придуманы Страуструпом и Деннисом Ритчи в 1981 году, а позже были добавлены в стандарт C комитетом ANSI C (X3J11), но в несколько урезанном виде: только readonly и после его переименования в
const
. - Ключевое слово
new
также взято из Simula. - Первоначальная реализация размещения объектов предполагала присваивание
this
в конструкторе. constructor
/destructor
в качестве имён соответствующих специальных методов были отвергнуты для уменьшения количества ключевых слов, а также для более очевидного синтаксиса.- Функции
new()
иdelete()
(старые имена конструктора и деструктора) в C with classes по умолчанию автоматически получали модификатор доступаpublic
. - Оператор
::
был введён для разрешения неоднозначности с точкой. - Изначально, переменные, введённые в списке инициализации
for
, были видны после его тела (из-за неточной формулировки «имена видны от места объявления до конца области»). Многие, наверное, сталкивались с этим в Borland C++.for (int i = 0; i < n; ++i) { // ... } if (i >= n) { // То же i, что объявлено в for.
- Вложенные области видимости классов были добавлены, позже убраны для совместимости с C и вновь добавлены ещё раз.
// Этот код работает и в C и в C++: struct Outer { struct Inner { }; }; // Но в C он эквивалентен следующему: struct Outer { }; struct Inner { };
static
по умолчанию для глобальных символов противоречил правилам C и по этой причине не был добавлен в C++.- Строгий контроль типов при вызове функций в C пришёл из C++.
- «Ключ к хорошему дизайну — глубокое понимание стоящих перед языком задач, а не включение самых передовых идей.»
- Абстрактные классы, типобезопасная компоновка и множественное наследование появились в версии Cfront 2.0.
- Произвольный порядок объявлений элементов класса доставил проблем и привел к некоторому неравноправию между типами и данными. Так делать разрешено:
int x; class X { int f() { return x; } // x означает X::x int x; };
А так уже запрещено:
typedef char *T; class Y { T f() { T a = 0; return a; } // Ошибка, т.к. T меняет своё значение на следующей строке. typedef f int T; };
Это же справедливо и для типов функций, ибо:
typedef int P(); class X { static P(Q1); // static int Q1(); static P Q2; // static int Q2(); };
- В Cfront временные объекты уничтожались в конце блока (сейчас — после вычисления выражения, если временный объект не привязывается):
const char *p; std::string s1 = ..., s2 = ...; if ((p = (s1 + s2).c_str()) && p[0]) { // Здесь можно было работать с p. // ... // Тут уничтожался временны объект, созданный выражением "s1 + s2". }
- Может ещё для кого-то будет новостью, но у Pascal также есть ISO/IEC стандарты (просто таких языков достаточно мало).
- Предложение о добавлении именованных аргументов было рассмотрено, но отклонено. (При чтении книги создалось впечатление, что отклонялось довольно большое количество различных предложений; впрочем, многое из этого всё же попало в стандарт спустя десятилетия.)
restrict
(noalias) указатели так и не были адаптированы из C несмотря на то, что были известны в начале 90-х.- Специальные символы вызвали проблемы распространения C в Европе из-за широкого использования 7-битных кодировок. Отсюда взялись триграфы, диграфы и специальные слова (
and
,or
и т.д.; см.). Всё это перекочевало и в C++. - Бюджет, который AT&T выделила на C++ за всё время, составляет примерно 3000$. Из них 1000$ пошла на рассылку рекламы C++ покупателям UNIX и 2000$ на первую конференцию, на которой всё было более чем скромно (даже на бумагу не хватило, волонтёры делали копии технической документации на бланках регистрации посетителей…). Тем не менее отсутствие какого-либо маркетинга в течении десятилетий не помешало широкому распространению языка.
Заключение
В заключение хотелось бы упомянуть несколько нестандартную структуру книги (поэтому порядок пунктов мог показаться странным) с повышением уровня детализации в каждой из частей, а также то, что она хорошо обрисовывает принципы, которых Страуструп пытается придерживаться и их обоснование. Поэтому она должна быть интересна далеко не только C++ программистам, но и многим, кто интересуется языками программирования в целом.
ссылка на оригинал статьи http://habrahabr.ru/post/270445/
Добавить комментарий