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


  if (!exists(dir)) {

    cout << "Path " << dir << " does not exist.\n";

    return 1;

  }


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

filesystem::canonical
возвращает другой объект класса
path
. Мы можем вывести на экран его напрямую, но перегруженный оператор
<<
класса
path
берет в кавычки пути к файлу. Чтобы этого избежать, можем отобразить путь к файлу с помощью методов
.c_str()
или
.string()
:


  cout << canonical(dir).c_str() << '\n';

}


5. Скомпилируем программу и поработаем с ней. Когда мы запустим ее в домашнем каталоге и передадим относительный путь к файлу

"src"
, она выведет на экран полный абсолютный путь к файлу.


$ ./normalizer src

/Users/tfc/src


6. Когда мы снова запускаем программу в домашнем каталоге, но даем ей запутанное относительное описание пути к файлу, в котором сначала прописывается вход в папку Desktop, потом прописывается выход из нее с помощью косвенного адреса

..
, затем входим в папку Documents и выходим из нее, чтобы в конечном итоге попасть в каталог
src
, программа отображает тот же путь файла, что и ранее!


$ ./normalizer Desktop/../Documents/../src

/Users/tfc/src


Как это работает

Этот начальный пример работы с

std::filesystem
мы сделали относительно коротким и прямолинейным. Мы инициализировали объект класса path на основе строки, которая содержит описание пути к файлу. Класс
std::filesystem::path
имеет самое первостепенное значение в тех ситуациях, когда мы используем библиотеку, связанную с файловой системой, поскольку с ним связано большинство функций и классов.

Функция

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

Наконец, мы использовали функцию

filesystem::canonical
для каталога, чтобы вывести на экран его нормализованную форму:


path canonical(const path& p, const path& base = current_path());


Функция

canonical
принимает путь к файлу и в качестве необязательного второго аргумента еще один путь к файлу. Второй путь
base
добавляется к пути файла
p
в том случае, если
p
является относительным. После этого функция
canonical
пытается убрать все косвенные адреса
.
и
..
.

Для вывода результата на экран мы использовали метод

.c_str()
, которому передали канонический путь к файлу. Мы сделали так потому, что перегруженный оператор
<<
для выходных потоков берет пути к файлам в кавычки, а это не всегда желательно.


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

Функция

canonical
генерирует исключение типа
fileystem_error
, если путь, который мы хотим привести к каноническому виду, не существует. Для предотвращения этого мы проверили наш путь к файлу с помощью функции
exists
. Но было ли достаточно данной проверки, чтобы необработанные исключения не генерировались? Нет.

Обе функции, как

exists
, так и
canonical
, способны генерировать исключения типа
bad_alloc
. Если бы эти исключения сгенерировались, кто-то мог бы утверждать, что программа все равно обречена. Более важная, а также гораздо более вероятная проблема возникает, когда где-то между проверкой существования файла и процессом приведения его к каноническому виду некто переименовывает или удаляет основной файл! В этом случае функция
canonical
сгенерирует сообщение об ошибке
filesystem_error
, хотя мы ранее уже убедились в том, что файл существует.

Большая часть функций файловой системы имеет еще одну перегруженную версию, которая принимает те же аргументы, а также ссылку на

std::error_code
:


path canonical(const path& p, const path& base = current_path());

path canonical(const path& p, error_code& ec);

path canonical(const std::filesystem::path& p,

               const std::filesystem::path& base,

               std::error_code& ec);


Таким образом, можно выбрать, окружать ли запросы к функциям файловой системы конструктами

try-catch
, или же проверять наличие ошибок вручную. Обратите внимание: это изменяет только поведение ошибок, связанных с файловой системой! Если в системе заканчивается память, то возможна генерация более сложных исключений, таких как
bad_alloc
, которые могут иметь или не иметь параметр
ec

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

В последнем примере мы уже приводили к каноническому виду/нормализовали пути файлов. Конечно же, класс

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

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

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


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

В данном примере мы будем работать с абсолютными и относительными путями к файлам, чтобы увидеть сильные стороны класса

path
и связанных с ним вспомогательных функций.


1. Сначала включаем все необходимые заголовочные файлы и объявляем, что используем пространства имен

std
и
filesystem
:


#include 

#include 


using namespace std;

using namespace filesystem;


2. Затем объявляем пример пути к файлу. На данный момент неважно, существует ли текстовый файл, на который он ссылается. Тем не менее есть функции, генерирующие исключения, если требуемого файла нет.


int main()

{

  path p {"testdir/foobar.txt"};


3. Сейчас мы познакомимся с четырьмя разными функциями библиотеки для работы с файловой системой. Функция

current_path
возвращает путь, в котором в данный момент выполняется программа, — так называемый рабочий каталог. Функция
absolute
принимает относительный путь к файлу наподобие нашего пути
p
и возвращает абсолютный, однозначный путь во всей файловой системе.

Функция

system_complete
делает практически то же самое, что и функция
absolute
в Linux, MacOS или других UNIX-подобных операционных системах. В Windows мы получим абсолютный путь к файлу, только вначале будет добавлено буквенное обозначение тома диска (например,
"C:"
). Функция
canonical
опять же делает то же самое, что и функция
absolute
, но потом дополнительно убирает все косвенные адреса, такие как "
.
" (сокращение для «текущий каталог») или "
..
" (сокращение для «один каталог вверх»). Мы рассмотрим работу с данными косвенными адресами в следующих шагах.


cout << "current_path      : " << current_path()

<< "\nabsolute_path   : " << absolute(p)

<