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
. Если данного пути к файлу не существует, то просто снова отображаем сообщение об ошибке: