Дополнительная информация
Еще один интересный вариант — ограниченная область видимости критических секций. Рассмотрим следующий пример:
if (std::lock_guard<std::mutex> lg {my_mutex}; some_condition) {
// Делаем что-нибудь
}
Сначала создается std::lock_guard
. Этот класс принимает мьютекс в качестве аргумента конструктора. Он запирает мьютекс в конструкторе, а затем, когда выходит из области видимости, отпирает его в деструкторе. Таким образом, невозможно забыть отпереть мьютекс. До появления С++17 требовалась дополнительная пара скобок, чтобы определить область, где мьютекс снова откроется.
Не менее интересный пример — это область видимости слабых указателей. Рассмотрим следующий фрагмент кода:
if (auto shared_pointer (weak_pointer.lock()); shared_pointer != nullptr) {
// Да, общий объект еще существует
} else {
// К указателю shared_pointer можно получить доступ, но он является нулевым
}
// К shared_pointer больше нельзя получить доступ
Это еще один пример с бесполезной переменной shared_pointer
. Она попадает в текущую область видимости, несмотря на то что потенциально является бесполезной за пределами условного блока if
или дополнительных скобок!
Выражения if
с инициализаторами особенно хороши при работе с устаревшими API, имеющими выходные параметры:
if (DWORD exit_code; GetExitCodeProcess(process_handle, &exit_code)) {
std::cout << "Exit code of process was: " << exit_code << '\n';
}
// Бесполезная переменная exit_code не попадает за пределы условия if
GetExitCodeProcess
— функция API ядра Windows. Она возвращает код для заданного дескриптора процесса, но только в том случае, если данный дескриптор корректен. После того как мы покинем этот условный блок, переменная станет бесполезной, поэтому она не нужна в нашей области видимости.
Возможность инициализировать переменные внутри блоков if
, очевидно, очень полезна во многих ситуациях, особенно при работе с устаревшими API, которые используют выходные параметры.
Всегда ограничивайте области видимости с помощью инициализации в выражениях if
и switch
. Это позволит сделать код более компактным, простым для чтения, а в случае рефакторинга его будет проще перемещать.
Новые правила инициализатора с фигурными скобками
В C++11 появился новый синтаксис инициализатора с фигурными скобками {}
. Он предназначен как для агрегатной инициализации, так и для вызова обычного конструктора. К сожалению, когда вы объединяли данный синтаксис с типом переменных auto
, был высок шанс выразить не то, что вам нужно. В C++17 появился улучшенный набор правил инициализатора. В следующем примере вы увидите, как грамотно инициализировать переменные в С++17 и какой синтаксис при этом использовать.
Как это делается
Переменные инициализируются в один прием. При использовании синтаксиса инициализатора могут возникнуть две разные ситуации.
1. Применение синтаксиса инициализатора с фигурными скобками без выведения типа auto
:
// Три идентичных способа инициализировать переменную типа int:
int x1 = 1;
int x2 {1};
int x3 (1);
std::vector<int> v1 {1, 2, 3};
// Вектор, содержащий три переменные типа int: 1, 2, 3
std::vector<int> v2 = {1, 2, 3};
// Такой же вектор
std::vector<int> v3 (10, 20);
// Вектор, содержащий десять переменных типа int,
// каждая из которых имеет значение 20
2. Использование синтаксиса инициализатора с фигурными скобками с выведением типа auto
:
auto v {1}; // v имеет тип int
auto w {1, 2}; // ошибка: при автоматическом выведении типа
// непосредственная инициализация разрешена
// только одиночными элементами! (нововведение)
auto x = {1}; // x имеет тип std::initializer_list<int>
auto y = {1, 2}; // y имеет тип std::initializer_list<int>