Параметры-ссылки и параметры-указатели
Когда же лучше использовать параметры-ссылки, а когда – параметры-указатели? В конце концов, и те и другие позволяют функции модифицировать объекты, эффективно передавать в функцию большие объекты типа класса. Что выбрать: объявить параметр ссылкой или указателем?
Как было сказано в разделе 3.6, ссылка может быть один раз инициализирована значением объекта, и впоследствии изменить ее нельзя. Указатель же в течение своей жизни способен адресовать разные объекты или не адресовать вообще.
Поскольку указатель может содержать, а может и не содержать адрес какого-либо объекта, перед его использованием функция должна проверить, не равен ли он нулю:
class X;
void manip( X *px )
{
// проверим на 0 перед использованием
if ( px != 0 )
// обратимся к объекту по адресу...
}
Параметр-ссылка не нуждается в этой проверке, так как всегда существует именуемый ею объект. Например:
class Type { };
void operate( const Type& p1, const Type& p2 );
int main() {
Type obj1;
// присвоим objl некоторое значение
// ошибка: ссылка не может быть равной 0
Type obj2 = operate( objl, 0 );
}
Если параметр должен ссылаться на разные объекты во время выполнения функции или принимать нулевое значение (ни на что не ссылаться), нам следует использовать указатель.
Одна из важнейших сфер применения параметров-ссылок – эффективная реализация перегруженных операций. При этом использование операций остается простым и интуитивно понятным. (Подробнее данный вопрос рассматривается в главе 15.) Разберем маленький пример. Представим себе класс Matrix (матрица). Хорошо бы реализовать операции сложения и присваивания “привычным” способом:
Matrix a, b, c;
c = a + b;
Эти операции реализуются с помощью перегруженных операторов – функций с немного необычным именем. Для оператора сложения такая функция будет называться operator+. Посмотрим, как ее определить:
Matrix // тип возврата - Matrix
operator+( // имя перегруженного оператора
Matrix m1, // тип левого операнда
Matrix m2 // тип правого операнда
)
{
Matrix result;
// необходимые действия
return result;
}
При такой реализации сложение двух объектов типа Matrix выглядит вполне привычно:
a + b;
но, к сожалению, оказывается совершенно неэффективным. Заметим, что параметры у нас передаются по значению. Содержимое двух матриц будет копироваться в область активации функции operator+(), а поскольку объекты типа Matrix весьма велики, затраты времени и памяти на создание копий могут быть совершенно неприемлемыми.
Представим себе, что мы решили использовать указатели в качестве параметров, чтобы избежать этих затрат. Вот модифицированный код operator+():
// реализация с параметрами-указателями
operator+( Matrix *ml, Matrix *m2 )
{
Matrix result;
// необходимые действия
return result;
}
Да, мы добились эффективной реализации, но зато теперь применение нашей операции вряд ли можно назвать интуитивно понятным. В качестве значений параметров-указателей требуется передавать адреса складываемых объектов. Поэтому для сложения двух матриц пришлось бы написать:
&a + &b; // допустимо, хотя и плохо
Хотя такая форма не может не вызвать критику, но все-таки два объекта сложить еще удается. А вот три уже крайне затруднительно:
// а вот это не работает
// &a + &b возвращает объект типа Matrix
&a + &b + &c;
Для того чтобы сложить три объекта, при подобной реализации нужно написать так:
// правильно: работает, однако ...
&( &a + &b ) + &c;
Трудно ожидать, что кто-нибудь согласится писать такие выражения. К счастью, параметры-ссылки дают именно то решение, которое требуется. Если параметр объявлен как ссылка, функция получает его l-значение, а не копию. Лишнее копирование исключается. И тип фактического аргумента может быть Matrix – это упрощает операцию сложения, как и для встроенных типов. Вот схема перегруженного оператора сложения для класса Matrix:
// реализация с параметрами-ссылками
operator+( const Matrix &m1, const Matrix &m2 )
{
Matrix result;
// необходимые действия
return result;
}
При такой реализации сложение трех объектов Matrix выглядит вполне привычно:
a + b + c;
Ссылки были введены в С++ именно для того, чтобы удовлетворить двум требованиям: эффективная реализация и интуитивно понятное применение.