C++17 STL Стандартная библиотека шаблонов — страница 111 из 119

f
. Ее можно вызвать с теми же аргументами, что и функцию
f
, поскольку
f2
подражает ей. Затем она возвращает вызываемый объект, который мы сохраняем в
f3
. Функция
f3
теперь захватывает
f
и аргументы
1
,
2
, но пока ничего не вызывает. Это все делается ради захвата.

Теперь при вызове функции

f3()
мы наконец получаем объект типа
future
, поскольку
f3()
делает вызов
async(launch::async,f,1,2);
! В некотором смысле семантическое значение
f3
заключается в следующем: «Получить захваченную функцию и аргументы, а затем передать их в вызов
std::async
».

Внутреннее лямбда-выражение, которое не принимает никаких аргументов, позволяет пойти по нестандартному пути. С его помощью можно настроить работу для параллельной отправки, но не нужно вызывать никаких блокирующих функций. Мы следуем тому же принципу в гораздо более сложной функции

async_adapter
:


template 

static auto async_adapter(F f)

{

  return [f](auto ... xs) {

    return [=] () {

      return async(launch::async, fut_unwrap(f), xs()...);

    };

  };

}


Данная функция также сначала возвращает функцию, которая подражает

f
, поскольку принимает те же аргументы. Затем эта функция возвращает вызываемый объект, который тоже не принимает аргументов. В результате упомянутый вызываемый объект наконец отличается от другой вспомогательной функции.

Каково значение строки

async(launch::async
,
fut_unwrap(f),xs()...);
? Часть
xs()...
означает предположение, что все аргументы, которые сохраняются в наборе параметров
xs
, являются вызываемыми объектами (как те, что мы постоянно создаем!) и, как следствие, вызываются без аргументов. Эти вызываемые объекты, постоянно создаваемые нами, производят значения типа
future
, для которых мы вызываем функцию
get()
. Здесь вступает в действие функция
fut_unwrap
:


template  static auto fut_unwrap(F f)

{

  return [f](auto ... xs) {

    return f(xs.get()...);

  };

}


Функция

fut_unwrap
просто преобразует функцию
f
в объект функции, который принимает диапазон аргументов. Данный объект вызывает функцию
.get()
для них всех и наконец перенаправляет их к
f
.

Возможно, вам потребуется время, чтобы это переварить. В нашей функции main цепочка вызовов

auto result (pconcat(...));
создавала большой вызываемый объект, который содержал все функции и все аргументы. К этому моменту мы не выполняли асинхронных вызовов. Затем, вызвав функцию
result()
, мы породили небольшую лавину вызовов
async
и
.get()
, которые выполнялись в правильном порядке, чтобы не заблокировать друг друга. Фактически вызовы
get()
не происходят до вызовов
async
.

В конечном счете мы наконец можем вызвать функцию

.get()
для значения типа
future
, которое вернула функция
result()
, и получить финальную строку. 

Глава 10Файловая система

В этой главе:

□ реализация нормализатора пути к файлу;

□ получение канонических путей к файлам из относительных путей;

□ составление списка всех файлов в каталоге;

□ реализация средства поиска текста в стиле

grep
;

□ реализация автоматического средства для переименования файлов;

□ реализация счетчика использования диска;

□ вычисление статистики о типах файлов;

□ реализация средства, которое уменьшает размер папки путем замены дубликатов символьными ссылками. 

Введение

Работа с путями файлов в файловой системе всегда утомительна, если у нас нет специальной библиотеки, поскольку при решении данной задачи необходимо учитывать множество условий.

Одни пути файлов являются абсолютными, другие — относительными, и, возможно, даже не являются прямыми, поскольку содержат косвенные адреса:

.
(текущий каталог) и
..
(родительский каталог). В то же время в различных операционных системах для разделения каталогов используется слеш
/
(Linux, MacOS и различные системы UNIX) или обратный слеш
\
(Windows). И конечно же, существуют разные типы файлов.

Поскольку каждая вторая программа, связанная с работой в файловой системе, нуждается в таком функционале, было бы здорово иметь новую библиотеку файловой системы в C++17 STL. Самое лучшее в данной ситуации то, что принцип работы одинаков для разных операционных систем, поэтому не требуется писать разные фрагменты кода для версий программ, поддерживающих разные операционные системы.

В этой главе мы сначала рассмотрим принцип работы класса

path
, поскольку он выступает самым важным элементом библиотеки. Затем увидим, насколько мощными, но простыми в использовании являются классы
directory_iterator
и
recursive_directory_iterator
при работе с файлами. В конце главы задействуем в примерах маленькие и простые инструменты, которые выполняют реальные задачи, связанные с файловой системой. С этого момента можно будет легко создавать более сложные инструменты. 

Реализуем нормализатор пути файла

Мы начинаем эту главу с очень простого примера, иллюстрирующего работу класса

std::filesystem::path
и вспомогательной функции, которая рационально нормализует пути к файлам.

Результатом данного примера является небольшое приложение, которое принимает любой путь к файлу и возвращает его в нормализованной форме. Нормализованный путь к файлу — абсолютный путь к файлу, не содержащий косвенных адресов

.
или
..
.

При реализации примера мы также увидим, на какие детали следует обратить внимание во время работы с этой базовой частью библиотеки, связанной с файловой системой.


Как это делается

В этом примере мы реализуем программу, которая всего лишь принимает путь к файлу как аргумент командной строки, а затем отображает его в нормализованной форме.


1. Файл с кодом начинается с директив

include
, а затем мы объявляем, что используем пространства имен
std
и
filesystem
:


#include 

#include 


using namespace std;

using namespace filesystem;


2. В функции

main
проверяем, предоставил ли пользователь аргумент командной строки. В случае отрицательного ответа выдаем сообщение об ошибке и отображаем на экране, как правильно работать с программой. Если путь к файлу все же был предоставлен, то создаем на его основе экземпляр объекта
filesystem::path
:


int main(int argc, char *argv[])

{

  if (argc != 2) {

    cout << "Usage: " << argv[0] << " \n";

    return 1;

  }

  const path dir {argv[1]};


3. Поскольку можно создавать объекты класса

path
из любой строки, нельзя быть уверенными в том, что полученный путь к файлу существует в файловой системе компьютера. Чтобы в этом убедиться, можем использовать функцию
filesystem::exists
. Если данного пути к файлу не существует, то просто снова отображаем сообщение об ошибке: