閉じる

C++で親ディレクトリを取得する方法:std::filesystemの使い方

C++においてファイルやディレクトリの操作を行う際、特定のファイルが置かれている「親ディレクトリ」のパスを取得したい場面は非常に多く存在します。

かつてのC++では、OSごとに異なるAPI(WindowsのWin32 APIやUnix系のPOSIXなど)を使い分ける必要がありましたが、C++17以降は標準ライブラリの「std::filesystem」を用いることで、プラットフォームに依存しない共通の記述が可能になりました。

本記事では、モダンC++における親ディレクトリ取得の決定版とも言える手法を、サンプルコードを交えて詳しく解説します。

std::filesystemによるパス操作の基本

C++17で導入されたstd::filesystem(以下、filesystemライブラリ)は、ファイルシステム上のパスを抽象化したstd::filesystem::pathクラスを提供しています。

このクラスを利用することで、文字列としてのパス操作ではなく、ファイルシステムとしての構造を考慮した高度な操作が可能になります。

pathクラスの役割

std::filesystem::pathは、単なる文字列のコンテナではありません。

OSごとのパス区切り文字(Windowsならバックスラッシュ、Linuxならスラッシュ)の違いを自動的に吸収し、ディレクトリ構造を「コンポーネント」の集合として管理します。

親ディレクトリを取得する際には、このpathオブジェクトが持つメンバ関数を活用することになります。

最も直接的な方法は、parent_path()関数を呼び出すことです。

parent_path()関数による親ディレクトリの取得

親ディレクトリを取得するための最も基本的かつ推奨されるメソッドがparent_path()です。

この関数は、対象となるパスから最後の要素を取り除いたパスを返します。

基本的な使い方のコード例

以下のプログラムは、指定したファイルのフルパスから、そのファイルが含まれる親ディレクトリを取得する例です。

C++
#include <iostream>
#include <filesystem> // std::filesystemを使用するために必要

namespace fs = std::filesystem;

int main() {
    // 操作対象となるパスを定義(文字列からパスオブジェクトを作成)
    fs::path targetPath = "/usr/local/bin/sample.txt";

    // parent_path()を使用して親ディレクトリを取得
    fs::path parent = targetPath.parent_path();

    // 結果を出力
    std::cout << "元のパス: " << targetPath << std::endl;
    std::cout << "親ディレクトリ: " << parent << std::endl;

    return 0;
}
実行結果
元のパス: "/usr/local/bin/sample.txt"
親ディレクトリ: "/usr/local/bin"

parent_path()の挙動と注意点

parent_path()の挙動を理解する上で重要なのは、「最後の要素を機械的に取り除く」という点です。

例えば、ディレクトリを指すパスの末尾に区切り文字があるかないかで、結果が変わる場合があります。

以下の表は、様々なパス入力に対してparent_path()がどのような結果を返すかをまとめたものです。

入力パスの例parent_path()の結果備考
/home/user/file.txt/home/user一般的なファイルの親取得
/home/user/dir//home/user/dir末尾にスラッシュがある場合
/home/ルート直下のディレクトリ
//ルートディレクトリ自体の親はルート
file.txt"" (空)相対パスで親が不明な場合

絶対パスへの変換と親ディレクトリ

相対パス(例: ./data/config.ini)から親ディレクトリを取得しようとすると、期待した結果が得られないことがあります。

これは、parent_path()が文字列ベースでパスを解析するため、そのファイルが実際にシステムのどこに位置しているかを関知しないためです。

確実に親ディレクトリのフルパスを取得したい場合は、まず相対パスを絶対パスに変換してから操作を行う必要があります。

absolute()関数との組み合わせ

std::filesystem::absolute()を使用すると、現在の作業ディレクトリを基準とした絶対パスを取得できます。

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    // カレントディレクトリにあるファイルを想定した相対パス
    fs::path relativePath = "settings.json";

    // 絶対パスに変換
    fs::path absPath = fs::absolute(relativePath);

    // 絶対パスから親ディレクトリを取得
    fs::path parentDir = absPath.parent_path();

    std::cout << "相対パス: " << relativePath << std::endl;
    std::cout << "絶対パス: " << absPath << std::endl;
    std::cout << "親ディレクトリの絶対パス: " << parentDir << std::endl;

    return 0;
}

実行結果(実行環境によって異なります):

相対パス: "settings.json"
絶対パス: "/home/user/projects/app/settings.json"
親ディレクトリの絶対パス: "/home/user/projects/app"

このように、「絶対パス化 → parent_path()」の順序で処理を行うことで、プログラムの実行場所によらず正確なディレクトリ情報を得ることができます。

繰り返し親を辿る方法(ルートまで遡る)

場合によっては、1つ上の親だけでなく、さらにその上の階層へと遡りたいことがあります。

例えば、プロジェクトのルートディレクトリを探すために、特定のファイルが見つかるまで親ディレクトリを探索し続けるようなケースです。

再帰的な親ディレクトリの取得

pathオブジェクト自体を更新しながらループを回すことで、ルートディレクトリに到達するまでのすべての親パスを表示できます。

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    fs::path p = fs::current_path(); // 現在の作業ディレクトリを取得

    std::cout << "探索開始パス: " << p << "\n" << std::endl;

    // パスが空でなく、かつルートディレクトリに到達するまで繰り返す
    while (p.has_parent_path() && p != p.root_path()) {
        p = p.parent_path();
        std::cout << "上の階層へ: " << p << std::endl;
    }

    return 0;
}

実行結果
探索開始パス: "/home/user/documents/work/cpp"

上の階層へ: "/home/user/documents/work"
上の階層へ: "/home/user/documents"
上の階層へ: "/home/user"
上の階層へ: "/home"
上の階層へ: "/"

このコードでは、has_parent_path()を使用して親が存在するかを確認し、root_path()と比較することでルートに到達したかを判定しています。

これにより、無限ループに陥ることなく安全にディレクトリ階層を遡ることが可能です。

実践的な活用シーン:実行ファイルのディレクトリ取得

プログラムの開発において、「自身の実行ファイル(.exeなど)が置かれているディレクトリ」を取得したいという要望は非常によくあります。

設定ファイルやリソースファイルを読み込む際に、実行ファイルの場所を基準にする必要があるからです。

これを実現するには、プログラムの第1引数(argv[0])またはOS固有の手段で実行パスを取得し、そこから親ディレクトリを抽出します。

実行ファイルパスからのディレクトリ抽出

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main(int argc, char* argv[]) {
    if (argc < 1) return 1;

    // 実行ファイルのパスを取得(argv[0]を利用)
    fs::path exePath = fs::absolute(argv[0]);

    // 実行ファイルが含まれるディレクトリが「アプリのルート」となる
    fs::path appRootDir = exePath.parent_path();

    std::cout << "実行ファイルの場所: " << exePath << std::endl;
    std::cout << "アプリのベースディレクトリ: " << appRootDir << std::endl;

    // リソースファイルへのパスを構築する例
    fs::path configPath = appRootDir / "config" / "settings.json";
    std::cout << "設定ファイルの予定地: " << configPath << std::endl;

    return 0;
}

この例で使用している/演算子は、filesystemライブラリにおいて「パスの結合」を意味します。

OSごとの区切り文字を気にする必要がないため、非常に安全にパスを構築できます。

発生しやすいエラーと対処法

filesystemライブラリは強力ですが、実行環境の状態(アクセス権限やディレクトリの存在有無)によっては例外が発生したり、意図しない挙動を示したりすることがあります。

存在しないパスに対する操作

parent_path()自体は単なる文字列解析(パスのパース)であるため、そのパスが実際に存在しなくてもエラーにはなりません。

しかし、その後にfs::exists()などで実在を確認せずにファイル操作を行うと、実行時エラーが発生します。

パーミッションの壁

親ディレクトリの情報を取得できても、そのディレクトリへの読み取り権限がない場合、中身をリストアップ(directory_iteratorなど)しようとすると例外がスローされます。

std::system_error例外をキャッチする準備をしておくか、error_code引数を受け取るオーバーロードを使用することが推奨されます。

C++
std::error_code ec;
fs::path p = fs::current_path(ec);
if (ec) {
    std::cerr << "エラー発生: " << ec.message() << std::endl;
}

このように、std::error_codeを利用することで、例外を投げずにエラーハンドリングを行うことが可能です。

まとめ

C++17で登場したstd::filesystemは、それまでの煩雑なディレクトリ操作を過去のものにしました。

親ディレクトリを取得するにはparent_path()を利用するのが最も効率的であり、absolute()と組み合わせることで確実な絶対パスを得ることができます。

また、/演算子によるパス結合や、root_path()によるルート判定などを組み合わせることで、複雑なディレクトリ構造のナビゲーションも簡潔に記述できることが分かりました。

モダンなC++開発において、これらの機能をマスターすることは、堅牢でクロスプラットフォームなアプリケーションを構築するための第一歩となります。

今回紹介した手法を活用し、直感的でメンテナンス性の高いファイル操作処理を実装してみてください。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!