CHAP8_2 (1018809)
Текст из файла
Глава 8.В(C). Ссылки
120. Ссылочные аргументы всегда должны быть константами.
121. Никогда не используйте ссылки в качестве результатов, пользуйтесь указателями.
Использование ссылочных аргументов в языке программирования вызвано четырьмя причинами:
-
Они нужны вам для определения конструктора копии.
-
Они нужны вам для определения перегруженных операций. Если вы определили:
some_class *operator+( some_class *left, some_class *right );
то вы должны сделать такое дополнение:
some_class x, y;
x = *(&x + &y)
Использование ссылок для аргумента и возвращаемого значения позволяет вам написать:
x = x + 1;
-
Вы часто хотите передать объекты по значению, исходя из логики. Например, вы обычно в функцию передаете тип double, а не указатель на double. Тем не менее, тип double представляет собой 8-байтовую упакованную структуру с тремя полями: знаковым битом, мантиссой и порядком. Передавайте в этой ситуации ссылку на константный объект.
-
Если объект какого-нибудь определенного пользователем класса обычно передается по значению, то используйте вместо этого ссылку на константный объект, чтобы избежать неявного вызова конструктора копии.
Ссылки в языке не предназначены для имитации Паскаля и не должны использоваться так, как используются в программе на Паскале.
Проблема ссылочных аргументов - сопровождение. В прошлом году один из наших сотрудников написал следующую подпрограмму:
void copy_word( char *target, char *&src ) // src является ссылкой на char*
{
while( isspace(*src) )
++src; // Инкрементировать указатель,
// на который ссылается src.
while( *src && !isspace(*src) )
*target++ = *src++; // Передвинуть указатель,
// на который ссылается src,
// за текущее слово.
}
Автор полагал, что вы будете вызывать copy_word() многократно. Каждый раз подпрограмма копировала бы следующее слово в буфер target и продвигала бы указатель в источнике.
Вчера вы написали следующий код:
f( const char *p )
{
char *p = new char[1024];
load( p );
char word[64];
copy_word( word, p );
delete( p ); // Сюрприз! p был модифицирован, поэтому
} // весь этот участок памяти обращается в кучу мусора!
Главная проблема состоит в том, что, глядя на вызов copy_word( word, p ), вы не получаете подсказки о возможном изменении p в подпрограмме. Чтобы добраться до этой информации, вы должны взглянуть на прототип этой функции (который, вероятно, скрыт на 6-ом уровне вложенности в заголовочном файле). Огромные проблемы при сопровождении.
Если что-то похоже на обычный вызов функции С, то оно должно и действовать как вызов обычной функции С. Если бы автор copy_word() использовал указатель для второго аргумента, то вызов выглядел бы подобным образом:
copy_word( word, &p );
Этот дополнительный знак & является решающим. Средний сопровождающий программист полагает, что единственная причина передачи адреса локальной переменной в другую функцию состоит в том, чтобы разрешить функции модифицировать эту локальную переменную. Другими словами, вариант с указателем является самодокументирующимся; вы сообщаете своему читателю, что этот объект изменяется функцией. Ссылочный аргумент не дает вам такой информации.
Это не значит, что вы должны избегать ссылок. Четвертая причина в начале этого раздела вполне законна: ссылки являются замечательным способом избегать ненужных затрат на копирование, неявных при передаче по значению. Тем не менее, для обеспечения безопасности ссылочные аргументы должны всегда ссылаться на константные объекты. Для данного прототипа:
f( const some_class &obj );
этот код вполне законен:
some_class an_object;
f( an_object );
Он похож на вызов по значению и при этом, что более важно, действует подобно вызову по значению - модификатор const предотвращает модификацию an_object в функции f(). Вы получили эффективность вызова по ссылке без его проблем.
Подведем итог: Я решаю, нужно или нет использовать ссылку, вначале игнорируя факт существования ссылок. Входные аргументы функций передаются по значению, а выходные - используют указатели на то место, где будут храниться результаты. Я затем преобразую те аргументы, которые передаются по значению, в ссылки на константные объекты, если эти аргументы:
-
являются объектами какого-то класса (в отличие от основных типов, подобных int);
-
не модифицируются где-то внутри функции.
Объекты, которые передаются по значению и затем модифицируются внутри функции, конечно должны по-прежнему передаваться по значению.
В заключение этого обсуждения рассмотрим пример из реальной жизни того, как не надо использовать ссылки. Объект CDocument содержит список объектов CView. Вы можете получить доступ к элементам этого списка следующим образом:
CDocument *doc;
CView *view;
POSITION pos = doc->GetFirstViewPosition();
while( view = GetNextView(pos) )
view->Invalidate();
Здесь есть две проблемы. Во-первых, у функции GetNextView() неудачное имя. Она должна быть названа GetCurrentViewAndAdvancePosition(), потому что она на самом деле возвращает текущий элемент и затем продвигает указатель положения (который является ссылочным аргументом результата) на следующий элемент. Что приводит нас к второй проблеме: средний читатель смотрит на предыдущий код и задумывается над тем, как завершается этот цикл. Другими словами, здесь скрывается сюрприз. Операция итерации цикла скрыта в GetNextView(pos), поэтому неясно, где она происходит. Ситуация могла быть хуже, если бы цикл был больше и содержал бы несколько функций, использующих pos в качестве аргумента - вы бы не имели никакого представления о том, какая из них вызывает перемещение.
Есть множество лучших способов решения этой проблемы. Простейший заключается в использовании в качестве аргумента GetNextView() указателя вместо ссылки:
POSITION pos = doc->GetFirstViewPosition();
while( p = GetNextView( &pos ) )
view->Invalidate();
Таким способом &pos сообщает вам, что pos будет модифицироваться; иначе зачем передавать указатель? Тем не менее, существуют и лучшие решения. Вот первое:
for( CView *p = doc->GetFirstView(); p ; p = p->NextView() )
p->Invalidate();
Вот второе:
POSITION pos = doc->GetFirstViewPosition();
for( ; pos ; pos = doc->GetNextView(pos) )
(pos->current())->Invalidate();
Вот третье:
CPosition pos = doc->GetFirstViewPosition();
for( ; pos; pos.Advance() )
( pos->CurrentView() )->Invalidate();
Вот четвертый:
ViewListIterator cur_view = doc->View_list(); // Просмотреть весь
// список отображений
// этого документа.
for( ; cur_view ; ++cur_view ) // ++ переходит к следующему отображению.
cur_view->Invalidate(); // -> возвращает указатель View*.
Вероятно, есть еще дюжина других возможностей. Все предыдущее варианты обладают требуемым свойством - в них нет скрытых операций и ясно, как происходит переход к "текущему положению".
122. Не возвращайте ссылки (или указатели) на локальные переменные.
Эта проблема проявляется и в С, где вы не можете вернуть указатель на локальную переменную. Не возвращайте ссылку на объект, который не существует после этого возврата. Следующий код не работает:
some_class &f()
{
some_class x;
// ...
return x;
}
Действительной проблемой здесь является синтаксис С++. Оператор return может располагаться на отдалении от определения возвращаемой величины. Единственный способ узнать, что на самом деле делает return x, - это взглянуть на заголовок функции и посмотреть, возвращает она ссылку,или нет.
123. Не возвращайте ссылки на память, выделенную оператором new.
Каждый вызов new должен сопровождаться delete - подобно malloc() и free(). Я иногда видел людей, старающихся избежать накладных расходов от конструкторам копии перегруженной бинарной операции подобным образом:
const some_class &some_class::operator+( const some_class &r ) const
{
some_class *p = new some_class;
// ...
return *p;
}
Этот код не работает, потому что вы не можете вернуться к этой памяти, чтобы освободить ее. Когда вы пишите:
some_class a, b, c;
c = a + b;
то a + b возвращает объект, а не указатель. Единственным способом получить указатель, который вы можете передать в оператор delete, является:
some_class *p;
c = *(p = &(a + b));
Это даже страшно выговорить. Функция operator+() не может прямо возвратить указатель. Если она выглядит подобным образом:
const some_class *some_class::operator+( const some_class &r ) const
{
some_class *p = new some_class;
// ...
return p;
}
то вы должны записать:
c = *(p = a + b);
что не так страшно, как в предыдущем примере, но все еще довольно плохо. Единственное решение этой задачи состоит в том, чтобы стиснуть зубы и вернуть объект:
const some_class *some_class::operator+( const some_class &r ) const
{
some_class obj;
// ...
return obj;
}
Если вам удастся вызвать конструктор копии в операторе return, то быть по сему.
Глава 8.Г(D). Конструкторы, деструкторы и operator=( )
Функции конструкторов, деструкторов и операций operator=() имеют ту особенность, что их создает компилятор в том случае, если не создаете вы. Генерируемый по умолчанию компилятором конструктор (не имеющий аргументов) и генерируемый компилятором деструктор нужны для создания указателя на таблицу виртуальных функций (подробнее об этом вскоре).
Генерируемый компилятором конструктор копии (чьим аргументом является ссылка на текущий класс) нужен еще по двум причинам, кроме таблицы виртуальных функций. Во-первых, код на С++, который выглядит как на С, должен и работать, как С. Так как правила копирования, которые относятся к классу, относятся также и к структуре, поэтому компилятор будет вынужден обычно генерировать конструктор копии в структуре, чтобы обрабатывать копирование структур в стиле С. Этот конструктор копии используется явно подобным образом:
some_class x; // конструктор по умолчанию
some_class y = x; // конструктор копии
но кроме этого он используется и неявно в двух ситуациях. Первой является вызов по значению:
some_class x;
f( some_class x ); // передается по значению, а не по ссылке.
// ... // вызывается конструктор копии для передачи x
f( x ); // по значению. Оно должно скопироваться в стек.
Второй является возврат по значению:
g() // Помните, что x - локальная, автоматическая переменная.
{ // Она исчезает после возвращения функцией значения.
some_class x; // Оператор return после этого должен скопировать x куда-нибудь
return x; // в надежное место (обычно в стек после аргументов).
} // Он использует для этой цели конструктор копии.
Генерируемая компилятором функция-операция operator=() нужна лишь для поддержки копирования структур в стиле С там, где не определена операция присваивания.
124. Операция operator=( ) должна возвращать ссылку на константу.
125. Присваивание самому себе должно работать.
Определение operator=( ) должно всегда иметь следующую форму:
class class_name
{
const class_name &operator=( const class_name &r );
};
const class_name &class_name::operator=( const class_name &r )
{
if( this != &r )
{
// здесь скопировать
}
return *this;
}
Аргумент, представляющий операнд источника данных, является ссылкой, чтобы избежать накладных расходов вызова по значению; это ссылка на константу, потому что аргумент не предназначен для модификации.
Эта функция возвращает ссылку, потому что она может это сделать. То есть вы могли бы удалить & из объявления возвращаемой величины, и все бы работало прекрасно, но вы бы получили ненужный вызов конструктора копии, вынужденный возвратом по значению. Так как у нас уже есть объект, инициализированный по типу правой части (*this), то мы просто можем его вернуть. Даже если возврат объекта вместо ссылки в действительности является ошибкой для функции operator=(), компилятор просто выполнит то, что вы ему приказали. Здесь не будет сообщения об ошибке; и на самом деле все будет работать. Код просто будет выполняться более медленно, чем нужно.
Наконец, operator=() должен возвращать ссылку на константу просто потому, что не хотите, чтобы кто-нибудь имел возможность модифицировать возвращенный объект после того, как произошло присваивание. Следующее будет недопустимым в случае возврата ссылки на константу:
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.















