The C++ Boost Libraries Part 5 - boost::filesystem

, in Computing

The standard C++ iostreams library is very good (well, some would say sort-of good) at reading and writing a file's contents, but it does so in such a way as to completely ignore file names This post was automatically imported from my old sandfly.net.nz blog. It may be a little out of date, since the boost::filesystem library has been updated several times since I wrote this it.. I am sure there was a good reason for this omission, but whatever it was is long out of date. There isn't even a standard way to iterate through a directory, for God's sake! You end up either falling back to the C standard library (ick) or using operating specific APIs just to find out how big a file is.

Most programs I have seen treat filesystem paths as strings. This "works" but is incorrect, filenames have many restrictions on them that do not apply to strings. Building up filenames by string concatenation is fraught with danger (I frequently see things like "C:\directory\filename.txt" in logfiles) and operations like splitting strings into filename components are annoying to get right.

Here is an example of file access using boost::filesystem:

filesystem::path usersDir( "/Users/andrew/" );
filesystem::path documentsDir( "Documents/" );
filesystem::path fileName( "in.txt" );

filesystem::ifstream inFile( usersDir/documentsDir/fileName );
while ( inFile )
{
  std::string s;
  std::getline( inFile, s );
  if (inFile)
  &nbsp;&nbsp;std::cout << s << "\n";
}

filesystem::path is the main class in boost::filesystem. Paths are usually built from strings (it is a templated class, filesystem::wpath also exists), but once constructed can be combined while following the rules of the target OS. Here we concatenate three paths together (using operator/() ) to open the file we want using the specialized version of ifstream.

Of course there are other things we can do with paths:

filesystem::path myPath("C:/Documents and Settings/All Users/temp/logfile.txt");
myPath.filename();  // "logfile.txt"
myPath.stem();  // filename without extension: "logfile"
myPath.extension();  // ".txt"
myPath.is_complete();  // true (on Windows) since the path has a root directory
myPath.parent_path().string(); // one directory up: "C:/Documents and Settings/All Users/temp/"

There are also useful non-member functions:

filesystem::create_directory( filesystem::path("/Users/andrew/new_dir" );
filesystem::create_directories( filesystem::path("/Users/andrew/new_dir/these/do/not/have/to/exist" );
filesystem::remove( filesystem::path( "/Users/andrew/unloved file" );
filesystem::exists( filesystem::path( "/Users/andrew/test" ); // returns true if path exists

There are also functions for renaming, copying and make symlinks or hardlinks to paths.

Another handy function is equivalent( path, path ) which returns true if the underlying operating system would consider the two paths as pointing to the same file/directory. On Windows equivalent( "cheese.txt", "CHEESE.TXT" ) is true, on POSIX it would be false.

Iterating Through Directories

Boost::filesystem makes this as easy as pie:

filesystem::path dir( "/Users/andrew/Documents" );

filesystem::directory_iterator end;
for ( filesystem::directory_iterator pos(dir);
      pos != end;
      ++pos )
{
  if ( is_regular_file( *pos ) )
  {
    std::cout << pos->path().filename() << " : " << filesystem::file_size( pos->path() ) << "\n";
  }
}

There is no way to apply a filter to the iteration, like you might with Win32's FindFirstFile() and I imagine it is less efficient since FFF returns a lot of details about the file in one hit, but it certainly is easier to use. There is also recursive_directory_iterator class which does the same thing but automatically enters subdirectories.

Exceptions

Finally, a word on exceptions. Boost::filesystem throws filesystem::filesysten_error like a monkey flings its own poo - with the slightest provocation. In my view this is absolutely correct, there are a million and one things that can go wrong when dealing with filesystems (disk errors, locking issues, permission problems, network trouble, etc) and exceptions are the right way to deal with unexpected (or perhaps exceptional) circumstances. Just beware of retro-fitting boost::filesystem into existing code that may not be prepared for exceptions escaping through it.

Don't worry, I am sure your code is perfectly exception safe. I am just mentioning this for all the other programmers out there.