Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 25
Текст из файла (страница 25)
орегагог [] (1) и в2. орегагог [] (1 ) возвращают временные объекты класса с]тат ген. Это в свою очередь означает з1.орегатог[](1).орегатог=(в2.орегатог()(1).орегатог с)гаг()) Встраивание во многих случаях делает производительность приема вполне приемлемой, а использование дружественных отношений для ограничения создания объектов типа с]тат геб гарантирует, что мы не получим проблем с «долго- живущими» временными объектами (см.
раздел 6.3.2). Данный прием был использован в классе Бсг1пд, доказавшем свою полезность. Однако для таких простых случаев, как доступ к отдельным символам, он выглядит усложненным и тяжеловесным. Поэтому я искал альтернативные решения. Одна такая возможность — составные операторы (см. раздел 11.6.3). 3.8. Константы В операционных системах доступ к некоторой области памяти часто прямо или косвенно контролируется двумя битами: первый дает информацию о том, можно ли пользователю писать в зту область, а второй — можно ли из нее читать. Эта идея показалась мне имеющей прямое отношение к С++, и я подумывал о том, чтобы разрешить для любого типа задавать атрибут геас]оп1у или мгфсеоп1у. Вот как излагается зта идея во внутренней записке, датированной январем 1981 г.
[8(гопз1гцр,1981Ъ]: «До настоящего времени в С было невозможно указать, что некоторый элемент донных должен быть доступен только для чтения, то есть его значение неизменно. Не было также способа наложить ограничения но то, что функция может делать с аргументами. Деннис Рычи отметил, что если бы существовал модификатор типо геат)оп1у, то легко можно было получить обе возможности; геаг)оп1у с)тат гаЫе [1024] г /* символы в таблице таЫе нельзя изменять */ ьпг т(геаг)оп1у 1пс * р) ( /* Г не может изменять данные, на которые указывает р */ /* */ Рождение С++ ППввИИИИИИИ Модификатор ге а«)оп1у служит для предотвращения изменения переменной. Ои показы- воет, что из всех способов доступа к переменной законны тол~ко те, которые ие измеияют ее значения».
Далее в записке говорится: «Модификатор геааоп1у применим также и к указателям. *геат(оп1у интерпретируется как «иеизмеияемый указатель ио». Например: геаооп1у 1пс * р; /* указатель на 1пс только для чтения */ 1пс * геайоп1у рр; /* неизменяемый указатель на 1пс */ геааоп1у 1пг * геаооп1у ррр; /* неизменяемый указатель на */ /* 1пс только для чтения */ В »тик примерах допустимо присвоить новое значение р, ио ие *р. Можно присвоить значение *рр, ио ие рр. Ни ррр, ии *ррр значение присвоить нельзя».
В той же записке введено понятие о ыгфсеоп1у: «Модификатор типо мгтсеоп1у аиологичеи геааоп1у, только запрещает чтение, о ие запись. Например: всгпсс деч1се гед1всегз ( геайоп1у 1пс 1прпс гед, зсагпв гед; ыгусеоп1у 1пс опсрпс гед, сопнпапс( гед; чоЫ 2(геааоп1у сваг * геааоп1у 2гом, ыг1сеоп1у с)тат * геат(оп1у со) ~* 1 может получить данные через 1гот, сохранить результаты в Со, но не может изменить ни тот, ни другой указатель */ ( /* */ ) 1пг * ыгйгеоп1у р; Здесь»+р недопустимо, ток как в качестве побочно«о эффекта подразумевает чтение предыдущего значения р, ио р=д допустимо». Это предложение акцентировало внимание на определении интерфейса, а не на включении в С символических констант.
Ясно, что значение с модификатором геас1оп1у — это символическая константа, но идея была шире. Первоначально я предлагал указатели на геас)оп1у, но не геас)оп1у-указатели. Благодаря короткой дискуссии с Деннисом Ричи возникла идея о механизме геас1оп1у/ мг1геоп1у, который был реализован и предложен внутренней группе по стандартизации С в стенах Ве!1 1.аЪз под председательством Ларри Рослера. Эта май первый опыт работы над стандартами.
Покидая совещание, я имел договоренность (то есть принятое большинством решение), что геас)оп1у будет включено в С— да-да, именно в С, а не в С тч(гЬ С!аазез или С++, следует только переименовать его в сопя с. К сожалению, подобные решения ни к чему не обязывают, вот почему Управление памятью й6ИИИИИИБ сонно 1по мах = 14; тотй 1(1пс 1) ( 1пс а[иахж1]; // константа мах используется в // константном выражении выткет (1) ( // константа иах используется в // константном выражении саве мах: ) ) тогда как в С (даже сегодня) необходимо аг)ег1пе мах 14 // поскольку, в С не разрешается использовать сопвс-объекты в константных выра- жениях. Из-за этого модификатор сопят в С не так полезен, как в С++.
Язык С остается зависимым от препроцессора, тогда как программистам на С++ доступ- ны типизированные константы с ограниченной областью действия. 3.9. Управление памятью Задолго до того как была написана первая программа на С зу)й С1аззез, я знал, что свободная (динамическая) память в языке с классами будет использоваться гораздо интенсивнее, чем в С.
По этой причине в С 1у(й С1аззез и были введены операторы пев/ и с(е1е се. Оператор пем, который одновременно выделяет память н инициализирует ее, заимствован из языка Б)шц!а. Оператор с)е1есе был необходимым дополнением, поскольку я не хотел, чтобы С 1у!гп С1аззез зависел от сборщика мусора (см. разделы 2.13 и 10.7).
Изложу аргументацию в пользу оператора пеи. Какая запись вам больше нравится, такая: х* р = пеи Х(2) с нашими компиляторами С ничего не произо~пло. Позже был образован комитет АНБ! С (ХЗ) 11), предложение о введении сопя с было представлено на его рассмотрение и стало частью АХИ!/150 С. Между тем я продолжил эксперименты с сопя с в С вчгЬ С!аьвез и обнаружил, что этот модификатор может служить заменой макросам для представления констант, только если глобальные сопвс-объекты используются как локальные в своих единицах компиляции. Лишь в этом случае компилятор мог без труда сделать вывод, что значение сопвс-объектов действительно не изменится. Знание этого факта позволяет использовать простые сопвс-объекты в константных выражениях, не выделяя под них память.
В С это правило оказалось непринятым. Например, в С++ можно написать Рождение С++ ШИИИИИИ«а или такая: ввгисС Х * р = (ввгцсв Х *) жа11ос(в1геог(всгцсг Х)); 11 (р == С) еггог("не хватило памяти"); р->1п1с(2); И где проще сделать ошибку? Отметим, что проверка на нехватку памяти ведется в обоих случаях. Только оператор пех при выделении памяти делает это неявно и может вызывать написанную пользователем функцию пем )запс11ег (2пс(, ~9А.З]. В то время достаточно часто звучали аргументы против: «нам это ни к чему» и «ведыт~о-то мог уже использовать пеи как идентификатор».
Разумеется, и то, и другое правильно. Итак, введение оператора пе»«упростило работу со свободной памятью и сделало ее менее подверженной ошибкам. Это способствовало широкому распространению оператора, поэтому из-за функции выделения памяти юа11ос ( ) из библиотеки С, использованной в реализации пеи, значительно снижалась производительность программы. Само по себе это не удивительно; вопрос — что с этим делать? То, что реальные программы тратили 50?» времени и даже больше внутри ща11ос (), было абсолютно неприемлемым. Я пришел к выводу, что очень эффективны операторы распределения и освобождения памяти, опрелеленныс в классе.
Основная идея заключалась в том, что свободная память, как правило, выделяется и освобождается для большого числа маленьких объектов, принадлежащих небольшому числу классов. Вынесите выделение памяти для таких объектов в отдельную функцию-распределитель, и тогда можно будет сэкономить и время, и память, а заодно уменьшить фрагментацию свободной памяти общего назначения.
Вспоминаю, как излагал Брайану Кернигану и Дугу Макилрою прием «присваивания указателю савв» (описан ниже) и резюмировал свою речь так: «Это уродливо, как смертный грех, но работает, и если у вас нет лучших предложений, то именно так я это и реализую». Предложений у них не оказалось, так что более красивого решения нам пришлось ждать до выхода версии 2.0, уже в С+-~ (см. раздел 10.2).
По умолчанию память для объекта выделялась «системой», не требуя от пользователя никаких действий. Чтобы отменить это, программист просто должен был присвоить значение указателю с)зйв. По определению с)зув указывает на объект, для которого вызвана функция-член. Например: с1авв Х ( // рц)в11с: х(1пс 1); // ); Х::Х((пв 1) ( С)з(в = гу а11ос(в1геог(Х)); // инициализация Контрольтипов ЯИИИИИКИ При каждом вызове конструктора х:: х ((пс ) память будет выделяться с помощью му а11ос ( ) . Этот механизм вполне справляется со своей непосредственной задачей и с некоторыми другими тоже, но он слишком низкоуровневый, плохо сочетается с управлением стеком и наследованием, подвержен ошибкам и должен применяться слишком часто, поскольку типичный класс имеет много конструкторов.