Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
Как правило, функции типа void используют оператор return для преждевременного завершения выполнения. Это аналогично использованию оператора break (см. раздел 5.5.1) для выход из цикла. Например, можно написать функцию swap(), которая не делает ничего, если значения идентичны:
void swap(int &v1, int &v2) {
// если значения равны, их замена не нужна; можно выйти сразу
if (v1 == v2)
return;
// если мы здесь, придется поработать
int tmp = v2;
v2 = v1;
v1 = tmp;
// явно указывать оператор return не обязательно
}
Сначала эта функция проверяет, не равны ли значения, и если это так, то завершает работу. Если значения не равны, функция меняет их местами. После последнего оператора присвоения осуществляется неявный выход из функции.
Функции, для возвращаемого значения которых указан тип void, вторую форму оператора return могут использовать только для возвращения результата вызова другой функции, которая возвращает тип void. Возвращение любого другого выражения из функции типа void приведет к ошибке при компиляции.
6.3.2. Функции, возвращающие значение
Вторая форма оператора return предназначена для возвращения результата из функции. Каждый случай возвращения значения типа, отличного от void, должен возвратить значение. Возвращаемое значение должно иметь тип, либо совпадающий, либо допускающий неявное преобразование (см. раздел 4.11) в тип, указанный для возвращаемого значения функции при определении.
Хотя язык С++ не может гарантировать правильность результата, он способен гарантировать, что каждое возвращаемое функцией значение будет соответствовать объявленному типу. Это может получиться не во всех случаях, компилятор попытается обеспечить возвращение значения и выход только через допустимый оператор return. Например:
// некорректное возвращение значения, этот код не будет откомпилирован
bool str_subrange(const string &str1, const string &str2) {
// размеры одинаковы: возвратить обычный результат сравнения
if (str1.size() == str2.size())
return str1 == str2; // ok: == возвращает bool
// найти размер меньшей строки; условный оператор см. раздел 4.7
auto size = (str1.size() < str2.size())
? str1.size() : str2.size();
// просмотреть все элементы до размера меньшей строки
for (decltype(size) i = 0; i != size; ++i) {
if (str1[i] != str2[i])
return; // ошибка #1: нет возвращаемого значения; компилятор
// должен обнаружить эту ошибку
}
// ошибка #2: выполнение может дойти до конца функции, так и
// не встретив оператор return
// компилятор может и не обнаружить эту ошибку
}
Оператор return в цикле for является ошибочным потому, что он не в состоянии вернуть значение. Эту ошибку компилятор должен обнаружить.
Вторая ошибка заключается в том, что функция не имеет оператора return после цикла. Если произойдет вызов этой функции со строкой, являющейся подмножеством другой, процесс выполнения минует цикл for. Однако оператор return для этого случая не предусмотрен. Эту ошибку компилятор может и не обнаружить. В этом случае поведение программы во время выполнения будет непредсказуемо.
Отсутствие оператора return после цикла, который этот оператор содержит, является особенно коварной ошибкой. Однако большинство компиляторов ее не обнаружит.
Как возвращаются значенияЗначения функций возвращаются тем же способом, каким инициализируются переменные и параметры: возвращаемое значение используется для инициализации временного объекта в точке вызова, и этот временный объект является результатом вызова функции.
В функциях, возвращающих локальные переменные, важно иметь в виду правила инициализации. Например, можно написать функцию, которой передают счетчик, слово и окончание. Функция возвращает множественную форму слова, если счетчик больше 1:
// возвратить множественную форму слова, если ctr больше 1
string make_plural(size_t ctr, const string &word,
const string &ending) {
return (ctr > 1) ? word + ending : word;
}
Тип возвращаемого значения этой функции — string, это значит, что возвращаемое значение копируется в точке вызова. Функция возвращает копию значения word или безымянную временную строку, полученную конкатенацией word и ending.
Когда функция возвращает ссылку, она, подобно любой другой ссылке, является только другим именем для объекта, на который ссылается. Рассмотрим, например, функцию, возвращающую ссылку на более короткую из двух переданный ей строк:
// возвратить ссылку на строку, которая короче
const string &shorterString(const string &s1, const string &s2) {
return s1.size() <= s2.size() ? s1 : s2;
}
Параметры и возвращаемое значение имеют тип ссылки на const string. Строки не копируются ни при вызове функции, ни при возвращении результата.
Никогда не возвращайте ссылку на локальный объектПо завершении работы функции все хранилища ее локальных объектов освобождаются (см. раздел 6.1.1). Поэтому после завершения работы функции ссылки на ее локальные объекты ссылаются на несуществующие объекты.
// катастрофа: функция возвращает ссылку на локальный объект
const string &manip() {
string ret;
// обработать ret некоторым образом
if (!ret.empty())
return ret; // ошибка: возвращение ссылки на локальный объект!
else
return "Empty"; // ошибка: "Empty" - локальная временная строка
}
Эта функция приведет к отказу во время выполнения, поскольку она возвращает ссылку на локальный объект. Когда функция завершит работу, область памяти, которую занимала переменная ret, будет освобождена. Возвращаемое значение будет ссылаться на ту область памяти, которая уже недоступна.
Оба оператора return возвращают здесь неопределенное значение — неизвестно, что будет, попробуй мы использовать значение, возвращенное функцией manip(). В первом операторе return очевидно, что функция пытается вернуть ссылку на локальный объект. Во втором случае строковый литерал преобразуется в локальный временный объект класса string. Этот объект, как и строка s, является локальным объектом функции manip(). Область памяти, в которой располагается временный объект, освобождается по завершении функции. Оба оператора return возвращают ссылки на области памяти, которые больше недоступны.
Чтобы удостовериться в безопасности возвращения значения, следует задаться вопросом: к какому существовавшему ранее объекту относится ссылка?
Возвращать указатель на локальный объект нельзя по тем же причинам. По завершении функции локальные объекты освобождаются, и указатель указывает на несуществующий объект.
Функции, возвращающие типы класса и оператор вызоваПодобно любому оператору, оператор вызова обладает порядком (ассоциативностью) и приоритетом (см. раздел 4.1.2). У оператора вызова тот же приоритет, что и у операторов "точка" и "стрелка" (см. раздел 4.6). Как и эти операторы, оператор вызова имеет левосторонний порядок. В результате, если функция возвращает указатель, ссылку или объект типа класса, результат вызова можно использовать для обращения к члену полученного объекта.
Например, размер более короткой строки можно определить следующим образом:
// обращение к методу size объекта строки, возвращенной shorterString
auto sz = shorterString(s1, s2).size();
Поскольку эти операторы имеют левосторонний порядок, результат вызова функции shorterString() будет левым операндом точечного оператора. Этот оператор выбирает метод size() объекта строки. Этот метод становится левым операндом второго оператора вызова.
Возвращаемая ссылка является l-значениемЯвляется ли вызов функции l-значением (см. раздел 4.1.1), зависит от типа возвращаемого значения функции. Вызовы функции, возвращающей ссылку, являются l-значением; другие типы возвращаемого значения являются r-значениями. Вызов функции, возвращающей ссылку, применяется таким же способом, как и любое другое l-значение. В частности, можно осуществлять присвоение результату вызова функции, возвращающей ссылку на неконстанту: