Boost

【C++】Boost.Filesystemでファイルサイズを確実に取得する方法

Boost.Filesystemを使うとC++で簡単にファイルサイズを得られます。

対象ファイルのパスをboost::filesystem::pathで指定し、boost::filesystem::file_sizeを呼び出すだけでバイト数が取得できます。

存在しないファイルやアクセス権限がない場合にはboost::filesystem::filesystem_errorがスローされるので、try-catchで受けて適切に対処すると安心です。

Boost.Filesystemの概要

Boost.Filesystemは、C++のプログラムからファイルシステムを操作するためのライブラリです。

標準ライブラリの一部ではありませんが、Boostライブラリの一つとして広く利用されています。

ファイルやディレクトリの操作、パスの管理、ファイル属性の取得など、多彩な機能を提供しており、クロスプラットフォーム対応も特徴です。

このセクションでは、Boost.Filesystemの基本的な概念と、その中でも特に重要なパス操作の基本と、ファイルシステムエントリの分類について解説します。

パス操作の基本

ファイルやディレクトリを扱う上で、パスの操作は非常に重要です。

Boost.Filesystemでは、boost::filesystem::pathクラスを用いてパスを表現し、操作します。

boost::filesystem::pathの特徴

boost::filesystem::pathは、ファイルやディレクトリのパスを表すクラスです。

文字列をラップし、パスの結合や分解、正規化などの操作を容易に行えます。

  • コンストラクタ: 文字列リテラルやstd::stringstd::wstringからパスを作成できます
  • パスの結合: operator/append()を使って複数のパスを結合可能です
  • パスの取得: string()native()メソッドで、パスの文字列表現を取得できます
  • パスの正規化: lexically_normal()を使えば、相対パスや...を解決した正規化パスを得られます

例として、以下のコードは複数のパスを結合し、正規化した例です。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path dir("/home/user");
    boost::filesystem::path filename("documents/../example.txt");
    boost::filesystem::path fullPath = dir / filename;
    // 正規化してパスを取得
    boost::filesystem::path normalizedPath = fullPath.lexically_normal();
    std::cout << "結合して正規化したパス: " << normalizedPath.string()
              << std::endl;
    return 0;
}
結合して正規化したパス: \home\user\example.txt

このように、boost::filesystem::pathはパスの操作を直感的に行える便利なクラスです。

コンパイルに通らない場合

Boost.Filesystem を使ったコードで「見慣れないエラー」「undefined reference」「ライブラリが見つからない」といったリンクエラーが出る場合は、Boost.System/Boost.Filesystem のインストールやリンク設定を確認してください。以下は代表的な環境ごとの対処例です。

Debian/Ubuntu 系 Linux

ライブラリのインストール

sudo apt-get update
sudo apt-get install libboost-system-dev libboost-filesystem-dev

コンパイル・リンク

g++ main.cpp -o myapp \
  -lboost_system \
  -lboost_filesystem


※ライブラリ名の前に -l を付け、-lboost_system-lboost_filesystem の順序が無難です。

macOS(Homebrew)

Boost のインストール

brew update
brew install boost

コンパイル・リンク

g++ main.cpp -o myapp \
  -I/usr/local/include \
  -L/usr/local/lib \
  -lboost_system \
  -lboost_filesystem

Windows(MSVC+vcpkg)

  1. vcpkg でインストール
vcpkg install boost-filesystem boost-system
  1. Visual Studio プロジェクト設定
  • 「追加のインクルード ディレクトリ」に
    C:\path\to\vcpkg\installed\<triplet>\include
  • 「追加のライブラリ ディレクトリ」に
    C:\path\to\vcpkg\installed\<triplet>\lib
  • リンクするライブラリに
    boost_filesystem-vc*.libboost_system-vc*.lib を追加

ソースビルドした Boost を使う場合

Boost.Build(b2)でビルド

./bootstrap.sh   # Windows は bootstrap.bat
./b2 --with-system --with-filesystem link=shared threading=multi

コンパイル・リンク

g++ main.cpp -o myapp \
  -I/path/to/boost \
  -L/path/to/boost/stage/lib \
  -lboost_system \
  -lboost_filesystem

CMake を使う場合

CMakeLists.txt の例

cmake_minimum_required(VERSION 3.5)
project(MyApp)

find_package(Boost REQUIRED COMPONENTS system filesystem)
if(Boost_FOUND)
  include_directories(${Boost_INCLUDE_DIRS})
  add_executable(myapp main.cpp)
  target_link_libraries(myapp ${Boost_LIBRARIES})
endif()

ビルド手順

mkdir build && cd build
cmake .. 
make

これらの手順でコンパイルできるようになります。

   cmake_minimum_required(VERSION 3.5)
   project(MyApp)

   find_package(Boost 1.60 REQUIRED COMPONENTS filesystem system)
   if(Boost_FOUND)
     include_directories(${Boost_INCLUDE_DIRS})
     add_executable(myapp main.cpp)
     target_link_libraries(myapp ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})
   else()
     message(FATAL_ERROR "Boost not found")
   endif()

これらを確認しても解決しない場合は、

  • ライブラリパスが正しいか(-L)
  • リンク順序(system → filesystem)
  • ビルドした Boost のバージョンとコンパイラが合っているか
  • -DBOOST_ALL_NO_LIB=ON で自動リンクを無効化して手動指定している箇所がないか

などを点検してみてください。これで大抵のリンクエラーは解消できます。

文字コード対応

Boost.Filesystemは、プラットフォームに依存した文字コードの違いに対応しています。

  • Windowsでは、boost::filesystem::pathは内部的にwchar_tを用いてUnicode文字列を扱います。これにより、日本語やその他の非ASCII文字も正しく扱えます
  • POSIX系(LinuxやmacOS)では、char型の文字列を用いてUTF-8エンコードされたパスを扱います

このため、プログラム内でパスを扱う際には、文字列のエンコーディングに注意が必要です。

特に、Windows環境ではwchar_tを使ったワイド文字列std::wstringを利用すると、文字化けやエンコーディングの問題を避けやすくなります。

Boost.Filesystemは、native()メソッドを使うことで、プラットフォームに適した文字列形式を取得できるため、エンコーディングの違いを吸収しやすくなっています。

#include <fcntl.h>
#include <io.h>
#include <boost/filesystem.hpp>
#include <iostream>
#include <locale>

int main() {
    // コンソールをワイド文字出力モードに切り替え
    _setmode(_fileno(stdout), _O_U16TEXT);

    // ワイド文字列リテラルでパスを構築
    boost::filesystem::path p(L"例のファイル.txt");
    // native() は wchar_t ベースの文字列を返す
    std::wcout << L"パス(ワイド文字): " << p.native() << L"\n";
    return 0;
}
パス(ワイド文字): 例のファイル.txt

このように、Boost.Filesystemはプラットフォームの文字コードの違いを吸収し、クロスプラットフォームなファイル操作を可能にしています。

ファイルシステムエントリの分類

ファイルシステムには、さまざまな種類のエントリがあります。

Boost.Filesystemでは、これらのエントリを判別し、適切に操作することが重要です。

ファイル/ディレクトリ/リンクの判別

boost::filesystemは、エントリの種類を判別するための関数を提供しています。

代表的なものは以下の通りです。

  • is_regular_file():通常のファイルかどうかを判定します
  • is_directory():ディレクトリかどうかを判定します
  • is_symlink():シンボリックリンクかどうかを判定します
  • exists():エントリが存在するかどうかを判定します

これらの関数は、boost::filesystem::status()boost::filesystem::symlink_status()と組み合わせて使います。

例として、指定したパスの種類を判定するコードを示します。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    // . はカレントディレクトリを指す
    boost::filesystem::path targetPath(".");
    boost::system::error_code ec;
    // パスの状態を取得
    auto status = boost::filesystem::status(targetPath, ec);
    if (ec) {
        std::cerr << "エラー: " << ec.message() << std::endl;
        return 1;
    }
    if (boost::filesystem::exists(status)) {
        if (boost::filesystem::is_regular_file(status)) {
            std::cout << "これは通常のファイルです。" << std::endl;
        } else if (boost::filesystem::is_directory(status)) {
            std::cout << "これはディレクトリです。" << std::endl;
        } else if (boost::filesystem::is_symlink(status)) {
            std::cout << "これはシンボリックリンクです。" << std::endl;
        } else {
            std::cout << "その他の種類のエントリです。" << std::endl;
        }
    } else {
        std::cout << "指定したパスは存在しません。" << std::endl;
    }
    return 0;
}
これはディレクトリです。

このコードでは、status()関数を使ってエントリの状態を取得し、その結果に基づいて種類を判定しています。

また、symlink_status()を使うと、シンボリックリンク自体の種類を判定でき、リンク先の実体を追跡しない点が特徴です。

これらの判定方法を理解しておくと、ファイル操作の際に適切な処理を行えるようになります。

たとえば、シンボリックリンクを無視したい場合や、リンク先の実体を操作したい場合など、状況に応じて使い分けることが可能です。

ファイルサイズ取得の基本

ファイルサイズを取得するためには、Boost.Filesystemが提供するfile_size関数を利用します。

この関数は、指定したファイルのサイズをバイト単位で返す便利な関数です。

以下に、その詳細について解説します。

file_size関数のシグネチャ

file_size関数のシグネチャは、次のようになっています。

#include <boost/filesystem.hpp>
boost::uintmax_t file_size(const boost::filesystem::path& p);

この関数は、boost::filesystem::path型の引数を受け取り、そのファイルのサイズをboost::uintmax_t型で返します。

boost::uintmax_tは、プラットフォームに依存しない符号なし整数型であり、非常に大きなファイルサイズにも対応できるようになっています。

戻り値の型と意味

file_sizeの戻り値は、ファイルのサイズをバイト単位で表すboost::uintmax_t型です。

具体的には、次のような特徴があります。

  • 正の整数値:ファイルの実際のサイズを示します
  • 0:空のファイルや、サイズが0のファイルを示します
  • 例外スローの可能性:ファイルが存在しない場合やアクセスできない場合には、boost::filesystem::filesystem_error例外をスローします

この関数を使う際には、例外処理を適切に行うことが重要です。

例外をキャッチしないと、プログラムが予期せずクラッシュする可能性があります。

例として、ファイルサイズを取得し、出力するコードを示します。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path filePath("example.txt");
    try {
        boost::uintmax_t size = boost::filesystem::file_size(filePath);
        std::cout << "ファイルサイズ: " << size << " バイト" << std::endl;
    } catch (const boost::filesystem::filesystem_error& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
    }
    return 0;
}
ファイルサイズ: 436 バイト

この例では、file_size関数を呼び出し、例外が発生した場合にはエラーメッセージを表示します。

対応Boostバージョンの確認(コンパイルできない場合)

file_size関数は、Boost 1.44以降のバージョンで利用可能です。

古いバージョンではサポートされていない場合があるため、使用前にBoostのバージョンを確認しておく必要があります。

  • Boost 1.44以降file_size関数が標準で利用可能です
  • Boost 1.43以前file_sizeは存在しないため、代替手段や自作の関数を用いる必要があります

Boostのバージョン確認は、ビルド環境やパッケージマネージャの情報から行います。

例えば、コマンドラインでboost/version.hppをインクルードし、バージョン番号を確認することも可能です。

#include <boost/version.hpp>
#include <iostream>
int main() {
    std::cout << "Boostバージョン: "
              << BOOST_VERSION / 100000 << "."
              << BOOST_VERSION / 100 % 1000 << "."
              << BOOST_VERSION % 100000 / 1000 << std::endl;
    return 0;
}
Boostバージョン: 1.85.8

このコードを実行すると、インストールされているBoostのバージョンが表示されます。

事前チェック

ファイルのサイズを取得する前に、対象のファイルが存在しているかどうかや、アクセス権限が適切かどうかを確認することは非常に重要です。

これらの事前チェックを行うことで、不要な例外やエラーを未然に防ぎ、プログラムの堅牢性を高めることができます。

ファイル存在の確認

boost::filesystem::exists()関数を使うと、指定したパスにファイルやディレクトリが存在しているかどうかを簡単に判定できます。

この関数は、存在すればtrueを返し、存在しなければfalseを返します。

exists関数の利用

exists()は、次のように使用します。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path targetPath("sample.txt");
    if (boost::filesystem::exists(targetPath)) {
        std::cout << "ファイルは存在します。" << std::endl;
    } else {
        std::cout << "ファイルは存在しません。" << std::endl;
    }
    return 0;
}
ファイルは存在しません。

このコードでは、sample.txtが存在しているかどうかを判定し、結果を出力します。

exists()は、ファイルだけでなくディレクトリやシンボリックリンクも対象にします。

アクセス権限の確認

ファイルにアクセスできるかどうかを確認するためには、boost::filesystem::status()permissions()を組み合わせて使います。

statusとpermissionsの組み合わせ

status()は、指定したパスのファイル属性情報を取得します。

これにより、ファイルの種類やアクセス権限などを調べることが可能です。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path targetPath("sample.txt");
    boost::system::error_code ec;
    auto fileStatus = boost::filesystem::status(targetPath, ec);
    if (ec) {
        std::cerr << "エラー: " << ec.message() << std::endl;
        return 1;
    }
    auto perms = fileStatus.permissions();
    if ((perms & boost::filesystem::owner_read) != 0) {
        std::cout << "所有者に読み取り権限があります。" << std::endl;
    } else {
        std::cout << "所有者に読み取り権限がありません。" << std::endl;
    }
    if ((perms & boost::filesystem::owner_write) != 0) {
        std::cout << "所有者に書き込み権限があります。" << std::endl;
    } else {
        std::cout << "所有者に書き込み権限がありません。" << std::endl;
    }
    if ((perms & boost::filesystem::owner_exe) != 0) {
        std::cout << "所有者に実行権限があります。" << std::endl;
    } else {
        std::cout << "所有者に実行権限がありません。" << std::endl;
    }
    return 0;
}
所有者に読み取り権限があります。
所有者に書き込み権限があります。
所有者に実行権限がありません。

この例では、status()で取得した属性情報から、所有者の読み取り・書き込み・実行権限を確認しています。

permissions()はビットフラグの集合を返し、これと標準の権限フラグをビット演算子&で比較します。

エラーコードの読み取り

status()exists()などの関数は、エラーが発生した場合にboost::system::error_codeを通じてエラー情報を返すことができます。

これにより、例外をスローさせずにエラーの詳細を取得できるため、エラー処理を柔軟に行えます。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path targetPath("sample.txt");
    boost::system::error_code ec;
    if (boost::filesystem::exists(targetPath, ec)) {
        std::cout << "ファイルは存在します。" << std::endl;
    } else {
        if (ec) {
            std::cerr << "エラー: " << ec.message() << std::endl;
        } else {
            std::cout << "ファイルは存在しません。" << std::endl;
        }
    }
    return 0;
}
ファイルは存在します。

このコードでは、exists()の結果とともにecを確認し、エラーがあれば詳細なメッセージを出力します。

これにより、ファイルの存在確認だけでなく、アクセス権やその他の問題も適切に把握できます。

エラー処理

Boost.Filesystemの関数は、エラーが発生した場合に例外をスローします。

特に、boost::filesystem::filesystem_errorは、ファイルシステム操作に関するエラーを表す例外クラスであり、エラーの詳細情報を保持しています。

これらの例外を適切に捕捉し、処理することは、堅牢なプログラムを作る上で不可欠です。

filesystem_errorの特徴

boost::filesystem::filesystem_errorは、std::exceptionを継承した例外クラスで、エラーの原因や詳細情報を提供します。

主な特徴は以下の通りです。

  • エラーメッセージwhat()メソッドを通じて、エラーの内容を文字列として取得できます
  • エラーコードcode()メソッドで、boost::system::error_codeオブジェクトを取得でき、エラーの種類や詳細な情報を確認できます
  • 発生場所:ファイルの存在確認やサイズ取得、属性変更など、ファイルシステムに関わる関数がエラー時にスロー

例として、file_size()を呼び出した際にエラーが発生した場合の例外の内容は次のようになります。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    try {
        boost::filesystem::path invalidPath("nonexistent.txt");
        auto size = boost::filesystem::file_size(invalidPath);
    } catch (const boost::filesystem::filesystem_error& e) {
        std::cerr << "例外発生: " << e.what() << std::endl;
        std::cerr << "エラーコード: " << e.code().message() << std::endl;
    }
    return 0;
}
例外発生: boost::filesystem::file_size: 指定されたファイルが見つかりません。 [system:2]: "nonexistent.txt"
エラーコード: 指定されたファイルが見つかりません。

この例では、存在しないファイルに対してfile_size()を呼び出すと、filesystem_error例外がスローされ、その内容とエラーコードの詳細が出力されます。

例外捕捉の方法

例外を適切に捕捉し、エラーに対応するためには、try -catch構文を用います。

以下に基本的な書き方と、より詳細な例外処理の例を示します。

try -catch構文の書き方

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    try {
        boost::filesystem::path targetPath("sample.txt");
        auto size = boost::filesystem::file_size(targetPath);
        std::cout << "ファイルサイズ: " << size << " バイト" << std::endl;
    } catch (const boost::filesystem::filesystem_error& e) {
        std::cerr << "ファイル操作中にエラーが発生しました。" << std::endl;
        std::cerr << "エラー内容: " << e.what() << std::endl;
        std::cerr << "エラーコード: " << e.code().message() << std::endl;
        // 必要に応じて追加のエラー処理を行う
    }
    return 0;
}
ファイル操作中にエラーが発生しました。
エラー内容: boost::filesystem::file_size: 指定されたファイルが見つかりません。 [system:2]: "sample.txt"
エラーコード: 指定されたファイルが見つかりません。

この例では、tryブロック内でファイルサイズを取得し、エラーが発生した場合はcatchブロックで例外を捕捉します。

e.what()にはエラーの詳細なメッセージが格納されており、e.code().message()でエラーコードの説明を取得できます。

カスタム例外ハンドラの例

より柔軟なエラー処理を行いたい場合、例外を捕捉した後に、エラーの種類に応じて異なる処理を行うことも可能です。

例えば、特定のエラーコードに基づいてリトライやログ出力を行う例です。

#include <boost/filesystem.hpp>
#include <iostream>
void handleFilesystemError(const boost::filesystem::filesystem_error& e) {
    if (e.code() == boost::system::errc::no_such_file_or_directory) {
        std::cerr << "エラー: ファイルが見つかりません。" << std::endl;
        // ファイルが存在しない場合の処理
    } else if (e.code() == boost::system::errc::permission_denied) {
        std::cerr << "エラー: アクセス権限がありません。" << std::endl;
        // 権限不足の場合の処理
    } else {
        std::cerr << "その他のエラー: " << e.what() << std::endl;
    }
}
int main() {
    try {
        boost::filesystem::path targetPath("restricted.txt");
        auto size = boost::filesystem::file_size(targetPath);
        std::cout << "ファイルサイズ: " << size << " バイト" << std::endl;
    } catch (const boost::filesystem::filesystem_error& e) {
        handleFilesystemError(e);
    }
    return 0;
}

この例では、handleFilesystemError()関数を定義し、エラーコードに応じて適切な処理を行います。

これにより、エラーの種類に応じた柔軟な対応が可能となります。

リンクファイルの扱い

ファイルシステムにおいて、リンクはファイルやディレクトリの参照を別の場所に作成する仕組みです。

リンクには大きく分けてシンボリックリンク(シンリンク)とハードリンクの2種類があります。

Boost.Filesystemでは、これらのリンクを適切に扱うための関数や概念が提供されています。

シンボリックリンクの解決

シンボリックリンクは、実体のファイルやディレクトリへの参照を持つ特殊なファイルです。

リンク先の実体を知るためには、is_symlink()関数を利用します。

is_symlinkの利用方法

is_symlink()は、指定したパスがシンボリックリンクかどうかを判定します。

リンクの場合はtrueを返し、そうでなければfalseです。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path linkPath("shortcut.lnk");
    boost::system::error_code ec;
    if (boost::filesystem::is_symlink(linkPath, ec)) {
        std::cout << linkPath << "はシンボリックリンクです。" << std::endl;
        // リンク先のパスを取得
        boost::filesystem::path targetPath = boost::filesystem::read_symlink(linkPath, ec);
        if (!ec) {
            std::cout << "リンク先: " << targetPath.string() << std::endl;
        } else {
            std::cerr << "リンク先の取得に失敗: " << ec.message() << std::endl;
        }
    } else {
        if (ec) {
            std::cerr << "エラー: " << ec.message() << std::endl;
        } else {
            std::cout << linkPath << "はシンボリックリンクではありません。" << std::endl;
        }
    }
    return 0;
}

この例では、is_symlink()を使ってリンクかどうかを判定し、リンクの場合はread_symlink()を用いてリンク先のパスを取得しています。

read_symlink()もエラーコードを返すため、エラー処理を併用することが推奨されます。

ハードリンクとの違い

ハードリンクとシンボリックリンクは、どちらもファイルの参照を作る仕組みですが、その性質や挙動には大きな違いがあります。

特徴シンボリックリンクハードリンク
実体のファイルとの関係別のファイルへの参照(リンクファイル)同じ inode を指す複数のディレクトリエントリ
参照先の種類ファイルやディレクトリのどちらも可能同じファイルのみ(ディレクトリには不可)
実体の削除実体が削除されるとリンクは無効になる(壊れたリンク)実体が削除されても、他のハードリンクが存在すればファイルは残る
パスの指定絶対パスまたは相対パス同じファイルシステム内でのみ有効
例外やエラーis_symlink()で判定可能is_hard_link()は標準APIに存在しないため、inode番号やリンク数を調べる必要がある

Boost.Filesystemでは、ハードリンクの存在や状態を直接判定する関数は提供されていません。

ただし、status()hard_link_count()を用いて、ファイルのinode番号やリンク数を調べることで、ハードリンクの数や状態を推測することは可能です。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path filePath("example.txt");
    boost::system::error_code ec;
    auto status = boost::filesystem::status(filePath, ec);
    if (ec) {
        std::cerr << "エラー: " << ec.message() << std::endl;
        return 1;
    }
    // inode番号やリンク数を取得してハードリンクの数を推測
    auto hardLinkCount = boost::filesystem::hard_link_count(filePath, ec);
    if (ec) {
        std::cerr << "リンク数の取得に失敗: " << ec.message() << std::endl;
    } else {
        std::cout << "ハードリンクの数: " << hardLinkCount << std::endl;
    }
    return 0;
}

この例では、hard_link_count()を使って、ファイルに対するハードリンクの数を取得しています。

複数のハードリンクが存在する場合、その数だけ同じ実体を指すエントリが存在します。

大容量ファイルの対応

64ビット環境においても、大容量ファイルの取り扱いには注意が必要です。

特に、boost::filesystem::file_size()boost::uintmax_tの範囲、オーバーフローのリスクについて理解しておくことが重要です。

boost::uintmax_tの範囲

boost::uintmax_tは、Boost.Filesystemでファイルサイズやリンク数などの大きな値を扱うために用意された符号なし整数型です。

64ビット環境では、一般的にこの型は次の範囲を持ちます。

範囲(概算)説明
boost::uintmax_t0 から約1.84×101964ビット符号なし整数の最大値

この範囲は、理論上、約18.4エクサバイト(EB)までのファイルサイズを表現可能です。

ただし、実際のファイルシステムやOSの制約により、これを超えるサイズのファイルは存在しません。

オーバーフロー回避のポイント

boost::filesystem::file_size()は、boost::uintmax_tを返しますが、非常に大きなファイルサイズを扱う場合にはオーバーフローのリスクがあります。

特に、複数のファイルのサイズを合計する際には注意が必要です。

  • 合計値の計算:複数のファイルサイズを合計する場合、boost::uintmax_tの最大値を超えないように注意します。超える場合は、64ビットの範囲外となり、誤った結果を返す可能性があります
  • 型の選択boost::uintmax_tは十分な範囲を持ちますが、将来的により大きな値を扱う必要が出てきた場合は、64ビットを超える型や、複数の値を組み合わせて管理する工夫が必要です
  • 事前チェック:合計計算前に、各ファイルのサイズを取得し、合計値がboost::uintmax_tの最大値を超えないかどうかを確認します

例として、複数ファイルのサイズを合計するコードを示します。

#include <boost/filesystem.hpp>
#include <boost/system/error_code.hpp>
#include <iostream>
#include <limits>
#include <vector>

int main() {
    // 調査対象のファイルリスト
    std::vector<boost::filesystem::path> files = {"file1.bin", "file2.bin",
                                                  "file3.bin"};

    boost::uintmax_t totalSize = 0;

    for (const auto& file : files) {
        boost::system::error_code ec;
        // ファイルサイズを取得(エラーコード受け取り版)
        auto size = boost::filesystem::file_size(file, ec);
        if (ec) {
            std::cerr << "ファイル " << file
                      << " のサイズ取得に失敗: " << ec.message() << std::endl;
            continue;
        }

        // オーバーフローをチェック
        if (totalSize > std::numeric_limits<boost::uintmax_t>::max() - size) {
            std::cerr << "合計サイズが最大値を超えます。計算を中断します。"
                      << std::endl;
            break;
        }

        totalSize += size;
    }

    std::cout << "合計ファイルサイズ: " << totalSize << " バイト" << std::endl;
    return 0;
}
合計ファイルサイズ: 38315 バイト

この例では、合計値がboost::uintmax_tの最大値を超えないかどうかを事前に確認し、超える場合は計算を中断しています。

分割ファイルの合計サイズ計算

大容量のファイルを複数に分割して保存している場合、それらの合計サイズを正確に計算することは重要です。

Boost.Filesystemを用いると、各分割ファイルのサイズを取得し、それらを合計することで、全体の容量を把握できます。

具体的には、次のような手順を踏みます。

  1. 分割ファイルの一覧を取得
  2. 各ファイルのサイズをfile_size()で取得
  3. 取得したサイズをboost::uintmax_tの変数に累積
  4. オーバーフローに注意しながら合計値を算出

この方法により、分割された複数のファイルの合計サイズを正確に把握でき、ストレージの管理やバックアップ計画に役立てることが可能です。

次のセクションでは、Boost.Filesystemを用いたファイルサイズの取得において、より堅牢なエラー処理の実践例について解説します。

複数ファイルのサイズ計測

複数のファイルの合計サイズを計測する場合、ディレクトリ内のファイルを効率的に走査し、必要に応じて再帰的にサブディレクトリも含めて集計することが求められます。

Boost.Filesystemは、そのための便利なツールを提供しています。

directory_iteratorの基本

boost::filesystem::directory_iteratorは、指定したディレクトリ内のエントリを一つずつ列挙するためのクラスです。

これを用いることで、ディレクトリ内のファイルやサブディレクトリを順次取得できます。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    // .はカレントディレクトリを指す
    boost::filesystem::path dirPath(".");
    boost::system::error_code ec;
    for (auto it = boost::filesystem::directory_iterator(dirPath, ec);
         it != boost::filesystem::directory_iterator(); ++it) {
        if (ec) {
            std::cerr << "エラー: " << ec.message() << std::endl;
            break;
        }
        std::cout << "エントリ: " << it->path().string() << std::endl;
    }
    return 0;
}
エントリ: .\example.txt
エントリ: .\log.txt
エントリ: .\main.cpp
エントリ: .\main.exe
エントリ: .\output.bin
エントリ: .\pch.h
エントリ: .\text.txt

この例では、directory_iteratorを使ってディレクトリ内のすべてのエントリを列挙しています。

ecを使えば、エラー発生時に例外をスローせずにエラー情報を取得できます。

再帰的走査の深度制御

ディレクトリのサブディレクトリも含めて再帰的に走査したい場合は、boost::filesystem::recursive_directory_iteratorを使用します。

これにより、階層構造を深さ制御しながら全てのファイルを列挙できます。

#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    // . はカレントディレクトリを指す
    boost::filesystem::path dirPath(".");
    boost::system::error_code ec;
    for (auto it = boost::filesystem::recursive_directory_iterator(dirPath, ec);
         it != boost::filesystem::recursive_directory_iterator(); ++it) {
        if (ec) {
            std::cerr << "エラー: " << ec.message() << std::endl;
            break;
        }
        std::cout << "エントリ: " << it->path().string() << std::endl;
    }
    return 0;
}
エントリ: .\.vscode
エントリ: .\.vscode\c_cpp_properties.json
エントリ: .\.vscode\launch.json
エントリ: .\.vscode\settings.json
エントリ: .\.vscode\tasks.json
エントリ: .\example.txt
エントリ: .\log.txt
エントリ: .\main.cpp
エントリ: .\main.exe
エントリ: .\output.bin
エントリ: .\pch.h
エントリ: .\text.txt

recursive_directory_iteratorは、デフォルトで全階層を深さ無制限に走査しますが、it.depth()メソッドを使えば、深さの制限も設定可能です。

#include <boost/filesystem.hpp>
#include <iostream>

int main() {
    namespace fs = boost::filesystem;
    fs::path dirPath("sample_directory");
    boost::system::error_code ec;

    // イテレータを例外ではなく error_code で初期化
    fs::recursive_directory_iterator it(dirPath, ec), end;
    if (ec) {
        std::cerr << "初期化エラー: " << ec.message() << std::endl;
        return 1;
    }

    const std::size_t maxDepth = 2;
    for (; it != end; /* ++it は下で行います */) {
        // 次の要素へ進む(エラー抑制版)
        it.increment(ec);
        if (ec) {
            std::cerr << "イテレーション中のエラー: " << ec.message() << std::endl;
            ec.clear();
            continue;
        }

        // 深さが maxDepth を超えたら、このディレクトリ以下をスキップ
        if (it.depth() > maxDepth) {
            it.pop();  // このサブディレクトリから抜ける
            continue;
        }

        std::cout << "エントリ: " << it->path().string() << std::endl;
    }

    return 0;
}

recursive_directory_iteratorオプション

recursive_directory_iteratorは、follow_directory_symlink()no_push_on_error()などのオプションを設定でき、走査の挙動を細かく制御できます。

  • follow_directory_symlink():シンボリックリンクのディレクトリも追跡

例として、シンボリックリンクを追跡しながら走査するコードは次の通りです。

#include <boost/filesystem.hpp>
#include <iostream>

namespace fs = boost::filesystem;

int main() {
    // 初期化エラーを受け取るための error_code
    boost::system::error_code ec;

    // ディレクトリのシンボリックリンクを再帰的にたどるオプションを指定してイテレータを構築
    fs::recursive_directory_iterator it(
        "sample_directory", fs::directory_options::follow_directory_symlink,
        ec);
    if (ec) {
        std::cerr << "初期化エラー: " << ec.message() << std::endl;
        return 1;
    }

    fs::recursive_directory_iterator end;
    for (; it != end; ++it) {
        std::cout << "エントリ: " << it->path().string() << std::endl;
    }

    return 0;
}

これらのオプションを適切に設定することで、必要な深さや追跡対象に応じた効率的なディレクトリ走査が可能です。

パフォーマンス考慮

大量のファイルや深いディレクトリ構造を扱う場合、パフォーマンスの最適化も重要です。

キャッシュ利用

directory_iteratorrecursive_directory_iteratorは、内部的にキャッシュを利用して効率的にエントリを列挙します。

ただし、走査中にエラーや例外が頻発するとパフォーマンスが低下するため、エラー処理を適切に行うことが重要です。

並列処理との組み合わせ

複数のスレッドやタスクを用いて、ディレクトリの走査やファイルサイズの計測を並列化することで、処理時間を短縮できます。

例えば、C++11のstd::threadstd::asyncを使って、複数のディレクトリを同時に走査したり、各ファイルのサイズ取得を並列化したりすることが可能です。

#include <boost/filesystem.hpp>
#include <future>
#include <vector>
#include <iostream>
boost::uintmax_t getFileSize(const boost::filesystem::path& p) {
    boost::system::error_code ec;
    auto size = boost::filesystem::file_size(p, ec);
    if (ec) {
        std::cerr << "エラー: " << ec.message() << std::endl;
        return 0;
    }
    return size;
}
int main() {
    std::vector<boost::filesystem::path> files = {"file1.bin", "file2.bin", "file3.bin"};
    std::vector<std::future<boost::uintmax_t>> futures;
    for (const auto& file : files) {
        futures.emplace_back(std::async(std::launch::async, getFileSize, file));
    }
    boost::uintmax_t totalSize = 0;
    for (auto& fut : futures) {
        totalSize += fut.get();
    }
    std::cout << "合計サイズ: " << totalSize << " バイト" << std::endl;
    return 0;
}
合計サイズ: 928406 バイト

この例では、std::asyncを使って複数のファイルサイズ取得を並列化し、処理時間を短縮しています。

次のセクションでは、Boost.Filesystemを用いたファイルサイズの取得において、より堅牢なエラー処理の実践例について解説します。

プラットフォーム別の注意点

ファイルシステムの操作やパスの扱いは、プラットフォームによって異なる点が多く存在します。

特に、WindowsとPOSIX系(LinuxやmacOS)では、パス表記や権限管理の仕組みが根本的に異なるため、それぞれの特徴と注意点を理解しておく必要があります。

Windowsでのパス表記

Windows環境では、パスの表記にいくつかの特徴があります。

特に、UNC(Universal Naming Convention)パスやドライブレターを用いたパス表記に注意が必要です。

UNCパス対応

UNCパスは、ネットワーク上の共有リソースにアクセスするためのパス表記です。

一般的な形式は次の通りです。

\\サーバ名\共有名\パス
\\server01\shared\folder\file.txt

Boost.Filesystemは、UNCパスを正しく扱うために、native()string()メソッドを使って適切にパスを取得し、ネットワークパスとして処理します。

ただし、以下の点に注意が必要です。

  • パスのエスケープ\\はエスケープシーケンスとして解釈されるため、文字列リテラル内では"\\\\server01\\shared\\folder\\file.txt"のように記述します
  • パスの正規化:UNCパスは特殊な形式のため、lexically_normal()を使う際には注意が必要です。正規化によってパスが変わる場合もあるため、必要に応じて適用します
  • APIの対応boost::filesystem::exists()file_size()はUNCパスに対応していますが、ネットワークの状態やアクセス権によりエラーが発生することもあります
#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path uncPath(R"(\\server01\shared\folder\file.txt)");
    if (boost::filesystem::exists(uncPath)) {
        auto size = boost::filesystem::file_size(uncPath);
        std::cout << "ファイルサイズ: " << size << " バイト" << std::endl;
    } else {
        std::cerr << "ファイルが存在しません。" << std::endl;
    }
    return 0;
}

この例では、UNCパスをraw文字列リテラルで記述し、存在確認とサイズ取得を行っています。

POSIX系での権限管理

LinuxやmacOSなどのPOSIX系システムでは、ファイルの権限管理がUnixの標準的な仕組みに基づいています。

これにより、ファイルのアクセス権や所有者、グループの設定が重要となります。

ファイルシステムごとの差異

POSIXシステムでは、chmodchownコマンドを使って権限や所有者を設定します。

Boost.Filesystemもこれらの情報を取得・操作できます。

  • 権限の取得status().permissions()を使って、ファイルのアクセス権をビットフラグとして取得します
  • 権限の設定permissions()関数を使って、権限を変更可能です
#include <boost/filesystem.hpp>
#include <iostream>
int main() {
    boost::filesystem::path filePath("example.txt");
    boost::system::error_code ec;
    auto perms = boost::filesystem::status(filePath, ec).permissions();
    if (ec) {
        std::cerr << "エラー: " << ec.message() << std::endl;
        return 1;
    }
    if ((perms & boost::filesystem::owner_read) != 0) {
        std::cout << "所有者に読み取り権限があります。" << std::endl;
    } else {
        std::cout << "所有者に読み取り権限がありません。" << std::endl;
    }
    // 権限の変更例(書き込み権限を追加)
    boost::filesystem::permissions(filePath, boost::filesystem::add_perms | boost::filesystem::owner_write, ec);
    if (ec) {
        std::cerr << "権限変更エラー: " << ec.message() << std::endl;
    } else {
        std::cout << "権限を変更しました。" << std::endl;
    }
    return 0;
}
所有者に読み取り権限があります。
権限を変更しました。
  • 所有者やグループの情報:Boost.Filesystemは、owner_name()group_name()を使って所有者やグループ名を取得できます。ただし、これらの操作はプラットフォームやファイルシステムによってサポート状況が異なるため、注意が必要です
  • 権限の差異:Windowsと異なり、POSIXシステムでは、ユーザやグループごとに詳細なアクセス制御リスト(ACL)を設定できる場合もありますが、Boost.Filesystemは基本的な権限操作に留まります

これらのプラットフォーム固有の注意点を理解し、適切に対応することで、クロスプラットフォームなファイル操作の信頼性と安全性を高めることができます。

標準ライブラリとの比較

C++17以降、標準ライブラリにstd::filesystemが導入され、ファイルシステム操作のための標準的なAPIが提供されています。

これにより、Boost.Filesystemとstd::filesystemの機能は非常に似通っていますが、いくつかの違いがあります。

std::filesystem::file_sizeとの相違点

C++標準バージョン要件

std::filesystem::file_sizeは、C++17標準の一部として導入されました。

利用するには、コンパイラと標準ライブラリがC++17に対応している必要があります。

  • コンパイラの対応例
    • GCC 7以降
    • Clang 5以降
    • MSVC 2017バージョン15.7以降
  • ライブラリのサポート
    • 一部の古い標準ライブラリでは未対応の場合もあるため、ビルド環境の確認が必要です

boost::filesystem::file_sizeは、Boostライブラリをリンクするだけで利用可能であり、C++標準のバージョンに依存しません。

ただし、Boostは外部ライブラリのため、ビルドや依存関係の管理が必要です。

実装パフォーマンス比較

boost::filesystem::file_sizestd::filesystem::file_sizeのパフォーマンスは、実装や環境によって異なりますが、一般的な傾向として次のような違いがあります。

  • boost::filesystem::file_size
    • Boostの実装は、クロスプラットフォーム対応のために抽象化層が多く、若干オーバーヘッドがある場合があります
    • ただし、最適化された実装も多く、実用上は十分高速です
  • std::filesystem::file_size
    • 標準ライブラリの一部として、よりネイティブに近い実装が期待されるため、若干高速になるケースもあります
    • ただし、標準ライブラリの実装はコンパイラやプラットフォームによって異なるため、一概に比較できません

実測値としては、ほぼ同等のパフォーマンスを示すことが多いですが、特定の環境やファイルシステムの違いにより差が出ることもあります。

例:std::filesystemを使ったファイルサイズ取得

#include <filesystem>
#include <iostream>
int main() {
    std::filesystem::path filePath("example.txt");
    try {
        auto size = std::filesystem::file_size(filePath);
        std::cout << "ファイルサイズ: " << size << " バイト" << std::endl;
    } catch (const std::filesystem::filesystem_error& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
    }
    return 0;
}
ファイルサイズ: 19 バイト

このコードは、boost::filesystem::file_sizeとほぼ同じ操作を行いますが、標準ライブラリのAPIを利用しています。

boost::filesystemstd::filesystemは、APIの互換性や機能の差異を理解した上で使い分けることが重要です。

特に、C++17未対応の環境や、既存のBoostライブラリを利用している場合は、Boost.Filesystemを継続して使う選択もあります。

一方、最新の標準規格に追従したい場合は、std::filesystemの利用を検討します。

応用例

Boost.Filesystemを活用したファイルサイズの取得やディレクトリ走査の技術は、さまざまな実用的な場面で役立ちます。

ここでは、具体的な応用例として、ログ監視ツールでの差分計測と、バックアップ前の容量チェックについて解説します。

ログ監視ツールでの差分計測

ログファイルの監視や管理において、ファイルの増加量を定期的に計測し、差分を把握することは重要です。

Boost.Filesystemを用いると、次のような流れで差分計測を行えます。

  1. 前回のログファイルサイズを記録:プログラムの起動時や定期的に、対象のログファイルのサイズを取得し、保存しておく。
  2. 現在のログファイルサイズを取得:次回の監視時に再度file_size()を呼び出し、現在のサイズを取得。
  3. 差分を計算:現在のサイズから前回のサイズを引き、増加したバイト数を算出。
  4. アラートやレポート:差分が一定閾値を超えた場合に通知やログ出力を行います。
#include <boost/filesystem.hpp>
#include <iostream>
#include <fstream>
int main() {
    boost::filesystem::path logFile("application.log");
    static uintmax_t previousSize = 0;
    boost::system::error_code ec;
    if (!boost::filesystem::exists(logFile, ec)) {
        std::cerr << "ログファイルが存在しません。" << std::endl;
        return 1;
    }
    auto currentSize = boost::filesystem::file_size(logFile, ec);
    if (ec) {
        std::cerr << "サイズ取得エラー: " << ec.message() << std::endl;
        return 1;
    }
    uintmax_t diff = currentSize - previousSize;
    std::cout << "ログの増加量: " << diff << " バイト" << std::endl;
    // 差分をファイルに保存(次回の比較用)
    std::ofstream ofs("size_record.txt");
    ofs << currentSize;
    ofs.close();
    previousSize = currentSize;
    return 0;
}
ログの増加量: 82 バイト

この例では、前回のサイズをファイルに保存し、次回の実行時に比較しています。

実運用では、定期的にこの処理をスケジューリングして、ログの増加を監視します。

バックアップ前の容量チェック

バックアップやデータ移行の前に、対象ディレクトリやファイルの合計容量を把握しておくことは、容量不足によるトラブルを未然に防ぐために重要です。

  1. ディレクトリ内の全ファイルを再帰的に走査recursive_directory_iteratorを用いて、サブディレクトリも含めてすべてのファイルを列挙。
  2. 各ファイルのサイズを取得し、合計file_size()を使って各ファイルのサイズを取得し、合計値を計算。
  3. 容量の閾値と比較:合計容量が、バックアップ先の空き容量や閾値を超えていないかを確認。
  4. 結果に応じた処理:容量不足の場合は警告を出す、処理を中断するなどの対応。
#include <boost/cstdint.hpp> // boost::uintmax_t のため
#include <boost/filesystem.hpp>
#include <iostream>
#include <limits> // std::numeric_limits

int main() {
    // . からの相対パスでディレクトリを指定
    boost::filesystem::path dirPath(".");
    boost::uintmax_t totalSize = 0;
    boost::system::error_code ec;

    // ディレクトリ再帰走査
    for (auto it = boost::filesystem::recursive_directory_iterator(dirPath, ec);
         it != boost::filesystem::recursive_directory_iterator(); ++it) {
        if (ec) {
            std::cerr << "走査エラー: " << ec.message() << std::endl;
            break;
        }
        if (boost::filesystem::is_regular_file(it->status())) {
            auto size = boost::filesystem::file_size(it->path(), ec);
            if (ec) {
                std::cerr << "サイズ取得エラー: " << ec.message() << std::endl;
                continue;
            }
            // オーバーフロー防止
            if (totalSize >
                std::numeric_limits<boost::uintmax_t>::max() - size) {
                std::cerr << "容量超過の可能性があります。計算中断。"
                          << std::endl;
                break;
            }
            totalSize += size;
        }
    }

    std::cout << "対象ディレクトリの合計容量: " << totalSize << " バイト"
              << std::endl;

    // 例:空き容量と比較(型を boost::uintmax_t に統一)
    boost::uintmax_t freeSpace =
        10ULL * 1024 * 1024 * 1024; // 例:10GB の空き容量
    if (totalSize > freeSpace) {
        std::cerr << "容量不足です。バックアップを中止します。" << std::endl;
    } else {
        std::cout << "容量十分です。バックアップを進めてください。"
                  << std::endl;
    }

    return 0;
}
対象ディレクトリの合計容量: 103720760 バイト
容量十分です。バックアップを進めてください。

このように、Boost.Filesystemを用いることで、事前に容量を把握し、適切なバックアップ計画を立てることが可能です。

これにより、容量不足による中断やデータ損失のリスクを低減できます。

ライブラリバージョン互換性

Boostライブラリは、頻繁に新しいリリースが行われ、機能追加やバグ修正が行われています。

そのため、特定のバージョン間での互換性や、APIの変更点を理解しておくことは、安定した開発とメンテナンスにとって重要です。

Boostリリースごとの変更点

Boostのリリースノートやドキュメントには、各バージョンでの主な変更点や新機能、非推奨APIの情報が記載されています。

特に、boost::filesystemに関しては、以下のような変更が過去のリリースで行われています。

  • APIの追加と改善:新しい関数やオプションの追加により、より柔軟な操作が可能になりました
  • 非推奨APIの導入と廃止:古いAPIや使いにくいAPIが非推奨となり、新しい推奨APIに置き換えられるケースが増えています
  • プラットフォーム対応の強化:WindowsやPOSIX系の差異を吸収するための内部実装の改善

例として、Boost 1.65ではboost::filesystem::status()のエラー処理に関する改善が行われ、エラーコードの取得や例外のスローに関する挙動が変更されました。

非推奨API移行のヒント

APIの非推奨化は、将来的なメンテナンス性やコードの安全性向上を目的としています。

古いAPIを使い続けると、将来的にサポートが終了したり、動作しなくなったりするリスクがあります。

  • 非推奨APIの確認:Boostのドキュメントやリリースノートで、非推奨となったAPI一覧を確認します
  • 置き換え候補の特定:非推奨APIの代替となる新しいAPIや推奨される書き方を調査します
  • 段階的な移行:既存のコードを一度に書き換えるのではなく、段階的に新APIに置き換え、動作確認を行います

例:boost::filesystem::status()のエラー処理の変更

旧API:

auto st = boost::filesystem::status(path);
if (boost::filesystem::exists(st)) {
    // 処理
}

新API:

boost::system::error_code ec;
auto st = boost::filesystem::status(path, ec);
if (!ec && boost::filesystem::exists(st)) {
    // 処理
}

このように、エラーコードを使ったエラー処理に移行することが推奨されます。

  • コードの互換性維持:複数のBoostバージョンをサポートする必要がある場合は、バージョン判定や条件コンパイルを用いて、適切なAPIを選択する工夫も有効です
#if BOOST_VERSION >= 106500
// Boost 1.65以降のAPI
#else
// それ以前のAPI
#endif

これらのポイントを押さえることで、Boostライブラリのバージョンアップに伴う影響を最小限に抑え、長期的に安定したコード運用を実現できます。

まとめ

この記事では、Boost.Filesystemを使ったファイルサイズの取得やディレクトリ走査の基本と応用例、プラットフォーム別の注意点、標準ライブラリとの比較、バージョン互換性について詳しく解説しました。

これらの知識を活用すれば、堅牢で効率的なファイル管理や監視ツールの構築が可能です。

関連記事

Back to top button