По умолчанию возвращаемое значение передается по значению, т.е. вызывающая функция получает копию результата вычисления выражения, указанного в инструкции return. Например:
Matrix grow( Matrix* p ) {
Matrix val;
// ...
return val;
}
grow() возвращает вызывающей функции копию значения, хранящегося в переменной val.
Такое поведение можно изменить, если объявить, что возвращается указатель или ссылка. При возврате ссылки вызывающая функция получает l-значение для val и потому может модифицировать val или взять ее адрес. Вот как можно объявить, что grow() возвращает ссылку:
Matrix grow( Matrix* p ) {
Matrix *res;
// выделим память для объекта Matrix
// большого размера
// res адресует этот новый объект
// скопируем содержимое *p в *res
return *res;
}
Если возвращается большой объект, то гораздо эффективнее перейти от возврата по значению к использованию ссылки или указателя. В некоторых случаях компилятор может сделать это автоматически. Такая оптимизация получила название именованное возвращаемое значение. (Она описывается в разделе 14.8.)
Объявляя функцию как возвращающую ссылку, программист должен помнить о двух возможных ошибках:
• возврат ссылки на локальный объект, время жизни которого ограничено временем выполнения функции. (О времени жизни локальных объектов речь пойдет в разделе 8.3.) По завершении функции такой ссылке соответствует область памяти, содержащая неопределенное значение. Например:
// ошибка: возврат ссылки на локальный объект
Matrix add( Matrix m1, Matrix m2 )
{
Matrix result:
if ( m1.isZero() )
return m2;
if ( m2.isZero() )
return m1;
// сложим содержимое двух матриц
// ошибка: ссылка на сомнительную область памяти
// после возврата
return result;
}
В таком случае тип возврата не должен быть ссылкой. Тогда локальная переменная может быть скопирована до окончания времени своей жизни:
Matrix add( ... )
• функция возвращает l-значение. Любая его модификация затрагивает сам объект. Например:
#include vector
int get_val( vectorint vi, int ix ) {
return vi [ix];
}
int ai[4] = { 0, 1, 2, 3 };
vectorint vec( ai, ai+4 ); // копируем 4 элемента ai в vec
int main() {
// увеличивает vec[0] на 1
get_val( vec.0 )++;
// ...
}
Для предотвращения нечаянной модификации возвращенного объекта нужно объявить тип возврата как const:
const int get_val( ... )
Примером ситуации, когда l-значение возвращается намеренно, чтобы позволить модифицировать реальный объект, может служить перегруженный оператор взятия индекса для класса IntArray из раздела 2.3.
7.4.1. Передача данных через параметры и через глобальные объекты
Различные функции программы могут общаться между собой с помощью двух механизмов. (Под словом “общаться” мы подразумеваем обмен данными.) В одном случае используются глобальные объекты, в другом – передача параметров и возврат значений.
Глобальный объект определен вне функции. Например:
int glob;
int main() {
// что угодно
}
Объект glob является глобальным. (В главе 8 рассмотрение глобальных объектов и глобальной области видимости будет продолжено.) Главное достоинство и одновременно один из наиболее заметных недостатков такого объекта – доступность из любого места программы, поэтому его обычно используют для общения между разными модулями. Обратная сторона медали такова:
* функции, использующие глобальные объекты, зависят от этих объектов и их типов. Использовать такую функцию в другом контексте затруднительно;
* при модификации такой программы повышается вероятность ошибок. Даже для внесения локальных изменений необходимо понимание всей программы в целом;
* если глобальный объект получает неверное значение, ошибку нужно искать по всей программе. Отсутствует локализация;
* используя глобальные объекты, труднее писать рекурсивные функции (Рекурсия возникает тогда, когда функция вызывает сама себя. Мы рассмотрим это в разделе 7.5.);
* если используются потоки (threads), то для синхронизации доступа к глобальным объектам требуется писать дополнительный код. Отсутствие синхронизации – одна из распространенных ошибок при использовании потоков. (Пример использования потоков при программировании на С++ см. в статье “Distributing Object Computing in C++” (Steve Vinoski and Doug Schmidt) в [LIPPMAN96b].)