Выбрать главу

Дополнительная информация

Еще один интересный вариант — ограниченная область видимости критических секций. Рассмотрим следующий пример:

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>