Грамматика для платформонезависимого представления путей в виде строк
Краткое описание хидера boost/filesystem/path.hpp
Специфичное для ОС представление путей
Предупреждение для POSIX и UNIX программистов
Хорошая программистская практика: относительные пути
Механизм проверки имен (name_check) по умолчанию
Примеры разбора путей на элементы
Функции библиотеки Boost.Filesystem работают с объектами класса path, который описан в хидере. Этот хидер также содержит объявления функций не-членов класса, выполняющих проверку ошибок.
Список операций с файлами и каталогами приведен в документации к boost/filesystem/operations.hpp.
Потоковые операции ввода/вывода описаны в документации к boost/filesystem/fstream.hpp.
Общие спецификации библиотеки Boost.Filesystem ко всем функциям (членам класса и нет), объявленным в указанном хидере.
Руководство по портабельности содержит обсуждение вопросов с именованием файлов, которые играют важную роль, если необходима платформонезависимость кода.
Класс path позволяет представлять пути в C++ программах независимо от платформы, используя обобщенную портабельную грамматику для представления путей в виде строк. Класс path связан с лексическими и синтаксическими аспектами путей. Путь не обязан существовать в файловой системе ОС, и даже может содержать имена, которые некорректны для используемой ОС.
Объяснение: Если бы функции библиотеки Boost.Filesystem работали с объектами std::strings или с C-строками, тогда эти функции давали бы только иллюзию портабельности, так как вызовы функции сами по себе были бы портабельны, но строки, которыми они оперировали - нет.
Объект класса path может
быть концептуализирован как содержащий последовательность строк. Каждая строка
является элементом пути. Каждый элемент представляет имя каталога,
или, в случае строки,
представляющей самый дальний от корня в иерархии каталогов элемент,
имя каталоги или файла. Имена ".." и
"." зарезервированы для представления концепций
родительского-каталога и текущего-каталога.
Концептуальное представление пути не зависит он какого-либо конкретного представления пути в виде одной строки.
Не обязательно, чтобы реализация класса path на самом деле была массивом строк, но концепция содержимого как последовательности строк позволяет размышлять о путях платформонезависимо.
Чтобы программы могли портабельно работать с путями в виде строк, класс path определяет грамматику портабельного обобщенного формата путей в виде строк, и имеет конструктор и операторы соединения, получающие аргументы как строки. Так как пользовательский ввод или сторонняя библиотека функций могут работать с путями в специфичном для ОС формате, то объявлен дополнительный конструктор, которому задается формат путей для текущей ОС.
Для получения содержимого объекта класса path в виде строки портабельного формата, или строки для пути к каталогу или файлу в формате ОС, объявлены специальные функции. Дополнительные функции доступа извлекают определенные части путей.
Эта грамматика представлена в виде расширенных BNF, с терминальными символами в кавычках:
path ::= [root] [relative-path] // an empty path is validroot ::= [root-name] [root-directory]root-directory ::= "/"relative-path ::= path-element { "/" path-element } ["/"]path-element ::= name | parent-directory | directory-placeholdername ::= char { char }directory-placeholder ::= "."parent-directory ::= ".."
Грамматика элемента root-name определяется реализацией. Элемент root-name не должен задаваться при обобщенном вводе. Этот элемент может быть частью строки, возвращаемой функциями-членами класса path, и может присутствовать в аргументе src конструктора path, если действует ОС-специфичная политика проверки.
char не может быть символом дроби ('/') или '\0'.
Хотя root-name определяется реализацией, желательно, чтобы элемент root-name имел грамматику, которая отличается от грамматики других элементов, и следовал соглашениям ОС.
Опциональный конечный "/" в относительном-пути допускается для удобства записи. Он не имеет значения и просто отбрасывается.
Будет или нет строка обобщенного формата на самом деле портбельной для конкретной ОС, зависит от используемых имен - см. руководство по портабельности.
Операции, модифицирующие объекты класса path, оставляют объекты path в канонической форме.
Пустой путь представляется в канонической форме.
Не-пустой путь преобразуется в каноническую форму при первом преобразовании в концептальную_модель:
заменой стоящих впереди элементов root-directory, parent-directory одним элементом root-directory. Объяснение: и POSIX и Windows описывают такую замену; спецификация такой замены для канонической формы гарантирует портабельность семантики для других ОС.
удалением каждого элемента directory-placeholder.
если после этого путь оказывается пустым, то добавляется один элемент directory-placeholder.
Нормализованная форма это та же каноническая форма, за исключением того, что рекурсивно удалены добавочные элементы name, parent-directory.
Таким образом, не-пустой путь в нормальной форме либо не имеет элементов directory-placeholders, либо состоит из единственного элемента directory-placeholder. Если он имеет элементы parent-directory, они должны быть перед всеми элементами name.
namespace boost
{
namespace filesystem
{
class path
{
public:
typedef bool (*name_check)( const std::string & name );
// compiler generates copy constructor,
// copy assignment, and destructor
// constructors:
path();
path( const std::string & src );
path( const char * src );
path( const std::string & src, name_check checker );
path( const char * src, name_check checker );
// append operations:
path & operator /= ( const path & rhs );
path operator / ( const path & rhs ) const;
// conversion functions:
const std::string & string() const;
std::string native_file_string() const;
std::string native_directory_string() const;
// modification functions:
path & normalize();
// decomposition functions:
path root_path() const;
std::string root_name() const;
std::string root_directory() const;
path relative_path() const;
std::string leaf() const;
path branch_path() const;
// query functions:
bool empty() const;
bool is_complete() const;
bool has_root_path() const;
bool has_root_name() const;
bool has_root_directory() const;
bool has_relative_path() const;
bool has_leaf() const;
bool has_branch_path() const;
// iteration:
typedef implementation-defined iterator;
iterator begin() const;
iterator end() const;
// default name_check mechanism:
static bool default_name_check_writable();
static name_check default_name_check( name_check new_check );
static name_check default_name_check();
private:
std::vector<std::string> m_name; // for exposition only
};
path operator / ( const char * lhs, const path & rhs );
path operator / ( const std::string & lhs, const path & rhs );
// name_check functions
bool portable_posix_name( const std::string & name );
bool windows_name( const std::string & name );
bool portable_name( const std::string & name );
bool portable_directory_name( const std::string & name );
bool portable_file_name( const std::string & name );
bool no_check( const std::string & name );
bool native( const std::string & name );
}
}
Ради демонстрации функции-члены класса path описаны как будто класс содержит приватный элемент std::vector<std::string> m_name. Действительная реализация может быть другой.
operator / (член класса или свободная функция) может сгенерировать исключение filesystem_error, если синтаксис пути не соответствует грамматике.
Замечание: Не существует гарантий, что объект path представляет путь, который будет корректным для текущей ОС. Путь может быть некорректным для ОС по причине наличия некорректных имен (слишком длинные, недопустимые символы, и так далее), или потому что это незавершенный путь. Некорректный путь будет нормально обнаружен при первом использовании, например в операциях или fstream библиотека Boost.Filesystem.
Предупреждение о портабельности: не существует гарантий, что объект path представляет путь, который будет портабелен для другой ОС. Путь может быть непортабельным по причине наличия слишком длинных имен или недопустимых символов. Механизм проверки имен по умолчанию предназначен для детектирования не-портабельных имен, либо функция проверки может быть задана в конструкторе path. Библиотека Boost.Filesystem содержит несколько функций для проверки имен, либо пользователи могут написать свои собственные.
Несколько членов класса path возвращают представление m_name в формате, специфичном для ОС. Этот формат определяется реализацией. Если элемент m_name содержит символы, которые недопустимы по правилам ОС, и есть однозначное преобразование между недопустимыми и допустимыми символами, то реализация должна выполнить преобразование символов. К примеру, если ОС не допускает символов в нижнем регистре в именах файлов и каталогов, эти символы заменяются на символы в верхнем регистре, если не возникает неоднозначности. Такая трансляция не применяется к форматам представления обобщенных путей.
Главное правило - использовать string(), когда требуется обобщенное строковое представление пути, и использовать либо native_directory_string(), либо native_file_string(), когда строковое представление форматируется для конкретной ОС.
Разница между представлениями, возвращаемыми методами string(), native_directory_string(), и native_file_string() иллюстрируется следующими примерами:
path my_path( "foo/bar/data.txt" ); std::cout << "; "string------------------: " << my_path.string() << '\n' << "native_directory_string-: " << my_path.native_directory_string() << << "native_file_string------: " << my_path.native_file_string() << '\n';
На POSIX системах результат будет:
string------------------: foo/bar/data.txt native_directory_string-: foo/bar/data.txt native_file_string------: foo/bar/data.txt
В Windows результат будет:
string------------------: foo/bar/data.txt native_directory_string-: foo\bar\data.txt native_file_string------: foo\bar\data.txt
В классической Mac OS результат будет:
string------------------: foo/bar/data.txt native_directory_string-: foo:bar:data.txt native_file_string------: foo:bar:data.txt
Для гипотетической ОС, использующей формат представления путей OpenVMS, результат был бы следующий:
string------------------: foo/bar/data.txt native_directory_string-: [foo.bar.data.txt] native_file_string------: [foo.bar]data.txt
Обратите внимание, что так как OpenVMS использует точку и как символ-разделитель каталогов, и как разделитель между именем файла и расширением, то native_directory_string() в данном примере возвращает бесполезный результат. Для этой ОС программист должен использовать этот путь только как путь к файлу. (В рекомендациях по платформонезависимости есть совет не использовать точки в именах каталогов).
POSIX и другие UNIX-подобные ОС имеют единый корень файловой системы, тогда как большинство других ОС имеют несколько корней. Много-корневые ОС требуют имя корня, к примеру диск, устройство, том или имя сетевого ресурса, чтобы путь был разрешен как действительный путь к файлу или каталогу. Из-за этого функции root() и root_directory() возвращают идентичные результаты в UNIX и других ОС с одним корнем, но разные результаты для много-корневых ОС. Поэтому использование неправильной функции неочевидно в UNIX-подобных системах, но приведет к непортабельному коду, который не будет работать на много-корневых системах. UNIX программистам следует быть осторожными в выборе между root() и root_directory(). В случае сомнений следует использовать root().
Аналогичное предупреждение применимо к has_root() и has_root_directory().
Прописывать в коде программы законченные пути является плохой практикой. Такие программы склонны к неустойчивой работе, так как они начинают сбоить если дерево каталогов реорганизуется, или программа переносится на другую машину или ОС.
Наиболее надежный способ получать законченные пути - прописывать в программе относительные пути. Когда требуется законченный путь, его можно получить несколькими способами:
Неявный способ. Пусть ОС завершает путь согласно со своим алгоритмом завершения путей. К примеру:
create_directory( "foo" ); // operating system will complete path
Пользовательский ввод. Зачастую лучше всего собирать путь с применением ОС-специфичной проверки имен, так что пользовательский ввод соответствует принятому в ОС формату путей, который удобен для пользователя. К примеру:
path foo( argv[1], native );
foo /= "foo";
initial_path(). Конкретно для консольных программ использование путей, относительных к пути запуска программы, является общераспространенной практикой. К примеру:
path foo( initial_path() / "foo" );
Алгоритмически. См. функции complete() и system_complete().
path();
Эффекты: создает по объект класса path со значением по умолчанию.
Послеусловия: path().empty()
path( const std::string & src, name_check checker ); path( const char * src, name_check checker ); path( const std::string & src ); path( const char * src );
Для форм с одним аргументом, default_name_check() используется
как механизм проверки.
Предусловие: src != 0.
Эффекты: выберите грамматику:
checker == native, то используйте для путей грамматику,
определенную реализацией ОС.checker == no_check, то используется
грамматика обобщенного строкового представления с
опциональным элементом root-name.Разбирает аргумент src на последовательность
имен, согласно грамматике, затем, для каждого имени в src, делается m_name.push_back( name ).
Исключения: Для каждого имени в src, исключение
генерируется если checker(
name ) возвращает false.
Послеусловия: m_name в канонической
форме. Только для формы с одним аргументом проверяется
!default_name_check_writable().
Объяснение: Конструкторы с одним аргументом не
объявлены как explicit, чтобы разрешить
преобразование строк в пути.
path & operator/=( const path & rhs );
Эффекты: Если любое из следующих условий удовлетворено, тогда делается m_name.push_back("/").
Затем добавляет rhs.m_name к m_name.
(Сноска: таким образом для Windows, (path("//share") /= "foo").string() равно "//share/foo")
Возвращает: *this
Послеусловия: m_name в канонической
форме.
Объяснение: Не будет ошибкой, если rhs
включает root-directory, потому что m_name
может быть относительным или пустым путем, и таким образом можно для
rhs задавать root-directory.
К примеру, в Windows следующий код должен выполниться
успешно:
path p( "c:", native );ative ); p /= "/foo"; assert( p.string() == "c:/foo" );
const path operator/ ( const path & rhs ) const;
Возвращает: path( *this ) /= rhs
Объяснение: Operator /
создан для того, чтобы вместе с
operator /= давать возможность удобно создавать
пути с произвольным числом элементов. К примеру, initial_path() / "src" /
test_name. Operator+ и
operator+= рассматривались как альтернативы,
но их слишком просто перепутать с такими же операторами в
std::string.
Первоначально какое-то время использовались
operator<< и
operator=<<, пока однажды
Dave Abrahams не указал, ччто / и /= соответствуют
обобщенному синтаксису путей.
Замечание: см. также функции не-члены класса operator/.
path & normalize();
Послеусловия: m_name в нормализованной форме.
Возвращает: *this
const std::string & string() const;
Возвращает: содержимое m_name,
отформатированное согласно правилам обобщенной грамматике
строкового представления путей.
Замечание: возвращаемая строка должна быть однозначна в соответствии с грамматикой. Это означает, что для ОС с именами корней (root-names), неотличимыми от имен относительных путей, имен с "/", или ОС, допускающих "." или ".." как имена каталогов и файлов, спецсимволы (escapes) или другие механизмы должны быть введены в грамматику для предотвращения я неоднозначностей. Это пока не сделано, так как ни одна из реализаций еще не сталкивалась с ОС, имеющей такие проблемы.
См. далее пример представления.
std::string native_file_string() const;
Возвращает: содержимое m_name, форматированное
для специфичного для ОС представления
пути.
См. далее - пример представления.
Объяснение названия: название намеренно уродливо, чтобы предупредить пользователя, что данная функция возвращает непортабельные результаты.
const std::string native_directory_string() const;
Возвращает: содержимое m_name, форматированное
для специфичного для ОС представления
пути.
См. далее - пример представления.
Объяснение названия: название намеренно уродливо, чтобы предупредить пользователя, что данная функция возвращает непортабельные результаты.
path root_path() const;
Возвращает: root_name() / root_directory()
Портабельно возращает копию полный путь к корню для пути, если таковой есть. См. примеры разбора путей.
std::string root_name() const;
Возвращает: Если !m_name.empty() && m_name[0]
является именем-корня, то возвращает
m_name[0], иначе возвращает пустую строку.
Портабельно возвращает копию корневого-имени для пути, если таковое есть. См. примеры разбора путей.
std::string root_directory() const;
Возвращает: Если путь содержит корневой-каталог,
то возвращает string("/"), иначе string().
Портабельно возвращает копию корневого-каталога для пути, если таковой есть. Возможны только варианты "/" или "". См. примеры разбора путей.
path relative_path() const;
Возвращает: новый путь, содержащий только относительный путь в исходном пути.
Портабельно возвращает часть исходного пути - относительный путь, если такой есть. См. примеры разбора путей.
std::string leaf() const;
Возвращает: empty() ? std::string() : m_name.back()
Обычно используется для получения имени файла или каталога без пути из объекта, получаемого от directory_iterator. См. примеры разбора путей.
path branch_path() const;
Возвращает: m_name.size() <= 1 ? path("") : x, where
x
is a path constructed from all the elements of m_name except the
last.
Обычно используется для получения родительского пути для переданного пользователем пути. См. примеры разбора путей.
bool empty() const;
Возвращает: string().empty().
Функция path::empty() определяет, пустая ли строка пути. Чтобы определить, пустой ли файл или каталог, идентифицируемый путем, используйте функцию empty() в хидере operations.hpp.
Объяснение названия: Контейнеры стандартной библиотеки C++ используют имя empty для аналогичных целей.
bool is_complete() const;
Возвращает: Для ОС с одним корнем файловой системы возвращает
результат has_root_directory(). Для файловых систем с многими
корнями возвращает has_root_directory() && has_root_name().
Объяснение названия: Другое имя - is_absolute() - вызывает сомнения и приводит к неправильному использованию, так как на ОС с многими корнями некоторые люди считают, что результат root_name() должен удовлетворять is_absolute(), тогда как некоторые - нет. См. частые вопросы.
Замечание: На большинстве ОС законченный (complete) путь всегда однозначно идентифицирует конкретный файл или каталог. На некоторых системах (classic Mac OS к примеру), даже законченный путь может быт неоднозначным в при некоторых условиях, так как эта ОС не требует однозначности.
bool has_root_path() const;
Возвращаетт: has_root_name() || has_root_directory()
bool has_root_name() const;
Возвращает: !root_name().empty()
bool has_root_directory() const;
Возвращает: !root_directory().empty()
bool has_relative_path() const;
Возвращает: !relative_path().empty()
bool has_leaf() const;
Возвращает: !leaf().empty()
bool has_branch_path() const;
Возвращает: !branch_path().empty()
typedef implementation-defined iterator;
Это константный итератор (const iterator), удовлетворяющий требованиям для двунаправленных итераторов стандартной библиотеки C++ (24.1). Итератор это класс (поэтому инкремент и декремент - operator++ и -- вызывают появление временных объектов (temporaries) ). Значение, ссылка и тип указателя это соответственно std::string, const std::string &, и const std::string *.
iterator begin() const;
Возвращает: m_path.begin()
iterator end() const;
Возвращает: m_path.end()
static bool default_name_check_writable();
Возвращает: true если ранее не была вызвана функция default_name_check.
static void default_name_check( name_check new_check );
Предусловие: new_check != 0
Послеусловия: default_name_check(new_check) &&
!default_name_check_writable()
Throws: if !default_name_check_writable()
static name_check default_name_check();
Возвращает: функцию default name_check.
Послеусловия: !default_name_check_writable()
path operator / ( const char * lhs, const path & rhs );
path operator / ( const std::string & lhs, const path & rhs );
Возвращает: path( lhs ) /= rhs
Сложно или невозможно написать портабельную программу, в которой нетпроверок, что используемые имена файлов и каталогов портабельны. Без автоматической верификации имен проверка становится скучной, подверженой ошибкам и неуклюжей. Но единственная функция не может быть применима для всех программ, и даже в пределах одного приложения разные пути или части пути могут потребовать верификации разными функциями проверки имен. Иногда проверка вообще не нужна.
Такие требования удовлетворяются с помощью функции проверки имен по умолчанию, которая наиболее часто применяется программами, и конструкторами класса path, позволяющими задать другую функцию проверки для менее общих случаев. Функция проверки по умолчанию может быть установлена программой, что позволяет применять проверку по умолчанию для большинства случаев.
Функция проверки имен по умолчанию устанавливается и возвращается статическими функциями-членами класса path, и поэтому похожа на глобальную переменную. Так как глобальные переменные считаются небезопасными [Wulf-Shaw-73], класс path позволяет только один раз установить функцию проверки имен по умолчанию, и только перед первым использованием. Это превращает опасную глобальную переменную в более безопасную глобалдьную константу. Но даже с таким защитным механизмом, возможность задать функцию проверки имен по умолчанию все же является очень мощным средством, и в некоторой степени опасной, так как может изменить поведение кода, спрятанного глубоко в недрах библиотеки или где-то еще. Поэтому изменение функции проверки имен по умолчанию следует делать только в том случае, если явное задание функции проверки как аргумента конструктора класса path не подходит.
Для дополнительных пояснений также см. частые вопросы.
Название функций: имена функций-членов класса path и имена свободных функций в хидере operations.hpp были выбраны отличающимися. Целью было избежать случаи, когда и foo.empty() и empty( foo ) корректны, но с совершенно разной семантикой. Однажды path::empty() была переименована в path::is_null(), но это вызвало множество опечаток, так как std::string::empty() часто используется неподалеку.
Функции разбора: без этих функций было бы невозможно выполнять портабельные манипуляции с путями. Немаловажное значение имеет и удобство.
Возвращение константных или не-константных объектов: в некоторых ранних версиях библиотеки, функции-члены возвращали объекты как константы (т.е. с квалификатором const). См. книгу Скотта Майерса Эффективный C++, пункт 21. Квалификаторы const были убраны по трем причинам. Во-первых, чтобы согласовать Boost.Filesystem с практикой стандартной библиотеки C++. Во-вторых, потому что неконстантные возвращаемые объекты позволяют применять иногда полезные выражения с возвращаемыми объектами. В третьих, потому что число исключаемых таким образом ошибок кодирования было исчезающе мало.
Чтобы исключить ошибки, было добавлено требование к path::iterator - он не должен быть не-классом.
В ряде случаев требуется извлечт определенные части из пути. Хотя любой разбор может быть осуществлен путем перебора всех элементов пути, удобнее использовать вспомогательные функции, которые проще, эффективнее и вызывают меньше ошибок.
Первый столбец в таблице содержит пример пути, форматированный функцией string(). Второй столбец показывает значение, получаемое при разыменовании итератора каждого элемента. Оставшиеся столбцы показывают результаты применения различных выражений.
| p.string() | элементы | p.root_ path() |
p.root_ name() |
p.root_ directory() |
p.relative_ path() |
p.root_ directory() / p.relative_ path() |
p.root_ name() / p.relative_ path() |
p.branch_ path() |
p.leaf() |
|---|---|---|---|---|---|---|---|---|---|
| Все системы | |||||||||
/ |
/ |
/ |
"" |
/ |
"" |
/ |
"" |
"" |
/ |
foo |
foo |
"" |
"" |
"" |
foo |
foo |
foo |
"" |
foo |
/foo |
/,foo |
/ |
"" |
/ |
foo |
/foo |
foo |
/ |
foo |
foo/bar |
foo,bar |
"" |
"" |
"" |
foo/bar |
foo/bar |
foo/bar |
foo |
bar |
/foo/bar |
/,foo,bar |
/> |
"" |
/ |
foo/bar |
/foo/bar |
foo/bar |
/foo |
bar |
. |
. |
"" |
"" |
"" |
. |
. |
. |
"" |
. |
.. |
.. |
"" |
"" |
"" |
.. |
.. |
.. |
"" |
.. |
../foo |
..,foo |
"" |
"" |
"" |
../foo |
../foo |
../foo |
.. |
|
| Windows | |||||||||
c: |
c: |
c: |
c: |
"" |
"" |
"" |
c: |
"" |
c: |
c:/ |
c:,/ |
c:/ |
c: |
/ |
"" |
/ |
c: |
c: |
/ |
c:.. |
c:,..de>c:,.. |
c: |
c: |
"" |
.. |
c:.. |
c:.. |
c: |
.. |
c:foo |
c:,foo |
c: |
c: |
"" |
foo |
foo |
c:foo |
c: |
foo |
c:/foo |
c:,/,foo |
c:/ |
c: |
/ |
foo |
/foo |
c:foo |
c:/ |
|
//shr |
//shr |
//shr |
//shr |
"" |
"" |
"" |
//shr |
"" |
//shr |
//shr/ |
//shr,/ |
//shr/ |
//shr |
/ |
"" |
/ |
//shr |
//shr |
/ |
//shr/foo |
//shr, |
//shr/ |
//shr |
/ |
foo |
/foo |
//shr/foo |
//shr/ |
foo |
prn: |
prn: |
prn: |
prn: |
"" |
"" |
"" |
prn: |
"" |
prn: |
Revised 04 июня, 2005
© Copyright Beman Dawes, 2002
Use, modification, and distribution are subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at www.boost.org/LICENSE_1_0.txt_1_0.txt)
последняя правка: 04.05.2005
библиотека BOOST C++
http://www.boost.org
перевод
Elijah Koziev
www.solarix.ru