Раньше функцию divide_remainder можно было реализовать следующим образом, используя выходные параметры:
bool divide_remainder(int dividend, int divisor,
int &fraction, int &remainder);
Получить к ним доступ можно так:
int fraction, remainder;
const bool success {divide_remainder(16, 3, fraction, remainder)};
if (success) {
std::cout << "16/3 is " << fraction << " with a remainder of "
<< remainder << '\n';
}
Многие все еще предпочитают делать именно так, а не возвращать пары, кортежи и структуры. При этом они приводят следующие аргументы: код работает быстрее, поскольку мы не создаем промежуточные копии этих значений. Но для современных компиляторов это неверно — они изначально оптимизированы так, что подобные копии не создаются.
Помимо того, что аналогичной возможности нет в языке C, возврат сложных структур в качестве выходных параметров долгое время считался медленным, поскольку объект сначала нужно инициализировать в возвращающей функции, а затем скопировать в переменную, которая должна будет содержать возвращаемое значение на вызывающей стороне. Современные компиляторы поддерживают оптимизацию возвращаемых значений (
return value optimization, RVO), что позволяет избежать создания промежуточных копий.
Ограничиваем область видимости переменных в выражениях if и switch
Максимальное ограничение области видимости переменных считается хорошим тоном. Иногда, однако, переменная должна получить какое-то значение, а потом нужно его проверить на соответствие тому или иному условию, чтобы продолжить выполнение программы. Для этих целей в С++17 была введена инициализация переменных в выражениях if и switch.
Как это делается
В данном примере мы воспользуемся новым синтаксисом в обоих контекстах, чтобы увидеть, насколько это улучшит код.
□ Выражение if. Допустим, нужно найти символ в таблице символов с помощью метода find контейнера std::map:
if (auto itr (character_map.find(c)); itr != character_map.end()) {
// *itr корректен. Сделаем с ним что-нибудь.
} else {
// itr является конечным итератором. Не разыменовываем.
}
// здесь itr недоступен
□ Выражение switch. Так выглядит код получения символа из пользовательского ввода и его одновременная проверка в выражении switch для дальнейшего управления персонажем компьютерной игры:
switch (char c (getchar()); c) {
case 'a': move_left(); break;
case 's': move_back(); break;
case 'w': move_fwd(); break;
case 'd': move_right(); break;
case 'q': quit_game(); break;
case '0'...'9': select_tool('0' - c); break;
default:
std::cout << "invalid input: " << c << '\n';
}
Как это работает
Выражения if и switch с инициализаторами по сути являются синтаксическим сахаром. Два следующих фрагмента кода эквивалентны:
До C++17:
{
auto var (init_value); if (condition) {
// Ветвь A. К переменной var можно получить доступ
} else {
// Ветвь B. К переменной var можно получить доступ
}
// К переменной var все еще можно получить доступ
}
Начиная с C++17:
if (auto var (init_value); condition) {
// Ветвь A. К переменной var можно получить доступ
} else {
// Ветвь B. К переменной var можно получить доступ
}
// К переменной var больше нельзя получить доступ
То же верно и для выражений switch.
До C++17:
{
auto var (init_value); switch (var) {
case 1: ...
case 2: ...
...
}
// К переменной var все еще можно получить доступ
}
Начиная с C++17:
switch (auto var (init_value); var) {
case 1: ...
case 2: ...
...
}
// К переменной var больше нельзя получить доступ
Благодаря описанному механизму область видимости переменной остается минимальной. До С++17 этого можно было добиться только с помощью дополнительных фигурных скобок, как показано в соответствующих примерах. Короткие жизненные циклы уменьшают количество переменных в области видимости, что позволяет поддерживать чистоту кода и облегчает рефакторинг.