開発者必見!Qt QFileInfo::fileName() を使いこなすための実践コード例

2025-05-31

QFileInfo とは?

QFileInfo クラスは、ファイルシステムのパスに関する情報(ファイル名、パス、サイズ、最終更新日時、パーミッションなど)を取得するために使用されます。ファイルやディレクトリの操作を行う際に非常に役立ちます。

QFileInfo::fileName() の機能

この関数は、フルパスからディレクトリ情報を取り除き、純粋なファイル名(またはディレクトリ名)のみを抽出して QString 型で返します。


例えば、以下のようなパスがあるとします。

  • my_directory/another_file.txt
  • /var/log/syslog
  • C:\Users\John\Pictures\holiday_2025\DSC0001.jpg
  • /home/user/documents/report.pdf

これらのパスに対して QFileInfo::fileName() を適用すると、以下の結果が得られます。

  • my_directory/another_file.txt -> another_file.txt
  • /var/log/syslog -> syslog
  • C:\Users\John\Pictures\holiday_2025\DSC0001.jpg -> DSC0001.jpg
  • /home/user/documents/report.pdf -> report.pdf

また、パスがディレクトリを指している場合でも、そのディレクトリ名が返されます。

  • C:\MyFolder\ -> MyFolder
  • /home/user/documents/ -> documents

使用例(C++ コード)

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QFileInfo fileInfo1("/home/user/documents/report.pdf");
    qDebug() << "File name 1:" << fileInfo1.fileName(); // 出力: "report.pdf"

    QFileInfo fileInfo2("C:/Users/John/Pictures/photo.png");
    qDebug() << "File name 2:" << fileInfo2.fileName(); // 出力: "photo.png"

    QFileInfo fileInfo3("/var/log/syslog");
    qDebug() << "File name 3:" << fileInfo3.fileName(); // 出力: "syslog"

    QFileInfo fileInfo4("my_directory/");
    qDebug() << "File name 4:" << fileInfo4.fileName(); // 出力: "my_directory"

    return a.exec();
}

なぜ QFileInfo::fileName() が便利なのか?

  • 可読性の向上
    ファイル名が必要な場面で、コードがより簡潔で分かりやすくなります。
  • クロスプラットフォーム対応
    Windows、macOS、Linux など、OS ごとのパス区切り文字(\/)の違いを意識することなく、正しくファイル名を抽出できます。QFileInfo が内部的にOSの差異を吸収してくれます。
  • パスからファイル名を簡単に抽出
    フルパス文字列から正規表現などを使わずに、簡単にファイル名だけを取り出せます。
  • パスが空文字列の場合、fileName() は空文字列を返します。
  • QFileInfo::fileName() は、ファイル名の拡張子も含めて返します。拡張子を除いたファイル名が欲しい場合は、QFileInfo::baseName() を使用します。


空の文字列が返される

最も一般的な問題の一つです。fileName() が空の文字列を返す場合、いくつかの原因が考えられます。

考えられる原因

  • 権限の問題
    ファイルやディレクトリへのアクセス権限がない場合、QFileInfo がその情報を取得できないことがあります。
  • シンボリックリンクの問題
    シンボリックリンクを扱っている場合、リンク先のファイルが存在しないなどの問題があると、QFileInfo が正しく情報を取得できないことがあります。
  • ディレクトリのパスにスラッシュで終わる場合
    /home/user/documents/ のようにディレクトリパスがスラッシュで終わる場合、fileName() はそのディレクトリ名(例: documents)を返しますが、空文字列を返すことは通常ありません。しかし、パスの末尾にスラッシュが複数あるなど、非常に異常なパスの場合には予期せぬ挙動が起こる可能性もゼロではありません。
  • 無効なパスが QFileInfo に渡された
    QFileInfo オブジェクトが、存在しないファイルやアクセスできないファイル/ディレクトリのパスで初期化された場合、ファイル情報を取得できず、fileName() も空になることがあります。

    • QFileInfo fileInfo("invalid/path/to/file.txt");

トラブルシューティング

  1. QFileInfo::exists() でパスの有効性を確認する
    fileName() を呼び出す前に、QFileInfo::exists() を使用して、指定されたパスが存在するかどうかを確認します。
    QFileInfo fileInfo("path/to/your/file.txt");
    if (!fileInfo.exists()) {
        qDebug() << "Error: File or directory does not exist!";
        // ここでエラー処理を行う
        return;
    }
    qDebug() << "File name:" << fileInfo.fileName();
    
  2. QFileInfo::isDir() や QFileInfo::isFile() で種類を確認する
    QFileInfo オブジェクトがファイルとディレクトリのどちらを指しているかを確認すると、デバッグに役立ちます。
    QFileInfo fileInfo("some_path");
    if (fileInfo.isDir()) {
        qDebug() << "Path is a directory. Directory name:" << fileInfo.fileName();
    } else if (fileInfo.isFile()) {
        qDebug() << "Path is a file. File name:" << fileInfo.fileName();
    } else {
        qDebug() << "Path is neither a file nor a directory.";
    }
    
  3. 入力パスをデバッグ出力で確認する
    QFileInfo を初期化する際に使用しているパス文字列が正しいかどうか、デバッグ出力 (qDebug()) で確認します。
  4. QFileInfo::absoluteFilePath() や QFileInfo::canonicalFilePath() を使用して正規化されたパスを確認する
    これらの関数は、シンボリックリンクを解決したり、絶対パスを返したりするため、問題のあるパスの特定に役立つことがあります。

スペースや特殊文字を含むファイル名/ディレクトリ名

ファイル名やディレクトリ名にスペース、括弧 (), & などの特殊文字が含まれる場合、一部の古いQtバージョンや特定のファイルシステム(特にLinuxのBTRFSなど)でQFileInfo::fileName()が空文字列を返す、または正しくない結果を返すという報告が稀にあります。

考えられる原因

  • URLエンコーディングとの混同
    パスがURLとしてエンコードされている場合、QFileInfo はそれを正しく解釈できないことがあります。QUrl::toLocalFile() などを使用して、URLからローカルファイルパスに変換してからQFileInfoに渡す必要があります。
  • QtのバージョンやOS/ファイルシステムの組み合わせによるバグ
    非常に稀ですが、特定の環境でこのような問題が発生する可能性があります。

トラブルシューティング

  1. Qtのバージョンを最新にする
    既知のバグであれば、新しいQtバージョンで修正されている可能性があります。
  2. QString::toLocalFile() を使用する(URLからパスを生成している場合)
    QUrl url("file:///home/user/my%20documents/file.txt");
    QFileInfo fileInfo(url.toLocalFile());
    qDebug() << "File name:" << fileInfo.fileName();
    
  3. パスを手動でデバッグする
    問題のパスを直接 QFileInfo に渡して、その挙動を確認します。
  4. 代替手段を検討する(最終手段)
    どうしても問題が解決しない場合、QDir::entryList() などを使ってディレクトリ内のファイル名をリストアップし、自力でパスを解析するなどの代替手段を検討する必要があるかもしれません。ただし、これはクロスプラットフォーム対応の利点を損なうため、推奨されません。

「vexing parse」問題 (C++ の初期化構文)

これは QFileInfo::fileName() 自体のエラーではありませんが、QFileInfo オブジェクトの初期化方法に関するC++の構文上の問題で、コンパイルエラーになることがあります。

問題のコード例

QFileInfo fileInfo(QFile(path)); // これは関数宣言と解釈されることがある

この構文は、コンパイラによって QFileInfo 型の fileInfo という名前の関数を宣言していると解釈されることがあります。この関数は QFile 型の引数を一つ取り、QFileInfo を返す、という意味になります。結果として、fileInfo.exists() などのメンバ関数を呼び出そうとするとコンパイルエラーになります。

トラブルシューティング

QFileInfo オブジェクトを初期化する際は、以下のいずれかの方法を使用してください。

  1. パス文字列を直接渡す
    QFileInfo fileInfo(path); // 最も推奨されるシンプルで正しい方法
    
  2. 一時的な QFile オブジェクトを作成して渡す
    QFile file(path);
    QFileInfo fileInfo(file); // 正しい初期化
    
  3. C++11 のブレース初期化を使用する
    QFileInfo fileInfo{QFile{path}}; // C++11 以降で利用可能
    

ファイルが存在しないのに fileName() が何かを返す

QFileInfo::fileName() は、パス文字列からファイル名部分を抽出するだけであり、ファイルが実際に存在するかどうかは関係ありません。例えば、QFileInfo("non_existent_file.txt")non_existent_file.txt を返します。

誤解

  • fileName() が何かを返したから、ファイルは存在するはずだ!」
  • 「ファイルが存在しないのに、fileName() が空じゃない!」

トラブルシューティング

これはエラーではなく、fileName() の正しい挙動です。ファイルが存在するかどうかを確認するには、必ず QFileInfo::exists() を使用してください。

QFileInfo fileInfo("/path/to/possibly/non_existent_file.txt");
qDebug() << "File name (regardless of existence):" << fileInfo.fileName();
if (fileInfo.exists()) {
    qDebug() << "File actually exists!";
} else {
    qDebug() << "File does NOT exist.";
}


基本的な使用例:ファイル名の取得

最も基本的な使い方です。特定のファイルパスからファイル名だけを取得します。

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug> // デバッグ出力用

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argc); // QCoreApplicationの初期化

    // 存在するファイルのパスを想定
    QString filePath1 = "/home/user/documents/report_2025.pdf";
    QFileInfo fileInfo1(filePath1);
    qDebug() << "パス 1: " << filePath1;
    qDebug() << "ファイル名 1: " << fileInfo1.fileName(); // 出力: "report_2025.pdf"

    // Windows形式のパスを想定
    QString filePath2 = "C:\\Users\\Public\\Pictures\\sample.jpg";
    QFileInfo fileInfo2(filePath2);
    qDebug() << "パス 2: " << filePath2;
    qDebug() << "ファイル名 2: " << fileInfo2.fileName(); // 出力: "sample.jpg"

    // カレントディレクトリのファイルを想定
    QString filePath3 = "my_app_settings.ini";
    QFileInfo fileInfo3(filePath3);
    qDebug() << "パス 3: " << filePath3;
    qDebug() << "ファイル名 3: " << fileInfo3.fileName(); // 出力: "my_app_settings.ini"

    // ディレクトリのパスを想定
    QString dirPath1 = "/var/log/"; // 末尾にスラッシュがあってもOK
    QFileInfo fileInfo4(dirPath1);
    qDebug() << "パス 4 (ディレクトリ): " << dirPath1;
    qDebug() << "ディレクトリ名 4: " << fileInfo4.fileName(); // 出力: "log"

    QString dirPath2 = "my_project_data"; // ディレクトリ名のみ
    QFileInfo fileInfo5(dirPath2);
    qDebug() << "パス 5 (ディレクトリ名のみ): " << dirPath2;
    qDebug() << "ディレクトリ名 5: " << fileInfo5.fileName(); // 出力: "my_project_data"

    return a.exec(); // イベントループを開始(今回の例ではあまり意味はないが、QCoreApplicationを使う一般的な形式)
}

解説

  • OS のパス区切り文字 (/ または \) の違いは、QFileInfo が内部で処理してくれるため、クロスプラットフォームで同じコードが動作します。
  • その後、fileName() メソッドを呼び出すだけで、パスの最後のコンポーネント(ファイル名またはディレクトリ名)が QString として返されます。
  • QFileInfo オブジェクトにパス文字列を渡して初期化します。

ファイルの存在確認とファイル名の取得

fileName() はパスから名前を抽出するだけで、ファイルが実際に存在するかどうかはチェックしません。ファイルが存在することを確認しながらファイル名を取得する例です。

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString existingFilePath = "/etc/hosts"; // 多くのシステムに存在するファイル
    QFileInfo existingFileInfo(existingFilePath);

    if (existingFileInfo.exists()) {
        qDebug() << "ファイルが存在します: " << existingFileInfo.absoluteFilePath();
        qDebug() << "ファイル名: " << existingFileInfo.fileName(); // 出力: "hosts"
    } else {
        qDebug() << "ファイルが存在しません: " << existingFilePath;
    }

    QString nonExistentFilePath = "/path/to/nonexistent/file.txt";
    QFileInfo nonExistentFileInfo(nonExistentFilePath);

    // ファイルは存在しないが、fileName()はパスから名前を抽出する
    qDebug() << "存在しないと想定されるパスのファイル名: " << nonExistentFileInfo.fileName(); // 出力: "file.txt"

    if (nonExistentFileInfo.exists()) {
        qDebug() << "ファイルが存在します: " << nonExistentFileInfo.absoluteFilePath();
    } else {
        qDebug() << "ファイルは存在しません: " << nonExistentFilePath; // こちらが出力される
    }

    return a.exec();
}

解説

  • fileName() は存在の有無に関わらず、パス文字列からファイル名部分を抽出して返します。この点に注意が必要です。
  • QFileInfo::exists() を使用して、ファイルやディレクトリが実際に存在するかどうかを確認します。

ファイル名と拡張子を分離する

fileName() は拡張子も含めて返します。拡張子を除いたファイル名(ベース名)や拡張子自体を取得するには、QFileInfo::baseName()QFileInfo::suffix() を併用します。

#include <QCoreApplication>
#include <QFileInfo>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString filePath = "/home/user/images/my_vacation_photo.jpg";
    QFileInfo fileInfo(filePath);

    qDebug() << "フルパス: " << filePath;
    qDebug() << "ファイル名 (fileName()): " << fileInfo.fileName();     // 出力: "my_vacation_photo.jpg"
    qDebug() << "ベース名 (baseName()): " << fileInfo.baseName();     // 出力: "my_vacation_photo"
    qDebug() << "拡張子 (suffix()): " << fileInfo.suffix();         // 出力: "jpg"

    QString noExtensionPath = "readme"; // 拡張子がない場合
    QFileInfo noExtensionInfo(noExtensionPath);
    qDebug() << "拡張子なしパス: " << noExtensionPath;
    qDebug() << "ファイル名: " << noExtensionInfo.fileName();   // 出力: "readme"
    qDebug() << "ベース名: " << noExtensionInfo.baseName();   // 出力: "readme"
    qDebug() << "拡張子: " << noExtensionInfo.suffix();     // 出力: "" (空文字列)

    QString multipleDotsPath = "document.part1.tar.gz"; // 複数のドットがある場合
    QFileInfo multipleDotsInfo(multipleDotsPath);
    qDebug() << "複数ドットパス: " << multipleDotsPath;
    qDebug() << "ファイル名: " << multipleDotsInfo.fileName(); // 出力: "document.part1.tar.gz"
    qDebug() << "ベース名: " << multipleDotsInfo.baseName(); // 出力: "document.part1.tar" (最後のドットまでがベース名)
    qDebug() << "拡張子: " << multipleDotsInfo.suffix(); // 出力: "gz"

    return a.exec();
}

解説

  • 複数のドットがある場合、suffix() は最後のドット以降のみを拡張子と見なすことに注意してください。
  • QFileInfo::suffix(): 最後のドットより後の部分(拡張子)を返します。ドットがない場合は空文字列を返します。
  • QFileInfo::baseName(): 最後のドットより前の部分を返します。

GUI アプリケーションでの例:ファイルダイアログから選択されたファイルのファイル名を表示

GUI アプリケーションでユーザーがファイルを選択し、そのファイル名を表示する例です。

#include <QApplication> // QApplicationはQCoreApplicationを継承
#include <QPushButton>
#include <QFileDialog>
#include <QFileInfo>
#include <QLabel>
#include <QVBoxLayout>
#include <QWidget>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget window;
    window.setWindowTitle("ファイル名表示");

    QVBoxLayout *layout = new QVBoxLayout(&window);

    QLabel *fileNameLabel = new QLabel("選択されたファイル名: (なし)");
    layout->addWidget(fileNameLabel);

    QPushButton *selectFileButton = new QPushButton("ファイルを選択...");
    layout->addWidget(selectFileButton);

    // ボタンがクリックされたときの処理
    QObject::connect(selectFileButton, &QPushButton::clicked, [&]() {
        // ファイル選択ダイアログを開く
        QString selectedFilePath = QFileDialog::getOpenFileName(
            &window,
            "ファイルを選択",
            QDir::homePath(), // デフォルトの開始ディレクトリ
            "すべてのファイル (*.*);;画像ファイル (*.png *.jpg *.jpeg);;テキストファイル (*.txt)"
        );

        if (!selectedFilePath.isEmpty()) { // ユーザーがファイルを選択した場合
            QFileInfo fileInfo(selectedFilePath);
            fileNameLabel->setText("選択されたファイル名: " + fileInfo.fileName());
            qDebug() << "選択されたファイルのフルパス: " << selectedFilePath;
            qDebug() << "抽出されたファイル名: " << fileInfo.fileName();
        } else {
            fileNameLabel->setText("選択されたファイル名: (キャンセルされました)");
            qDebug() << "ファイル選択がキャンセルされました。";
        }
    });

    window.show();

    return app.exec();
}
  • そのパスを QFileInfo に渡して初期化し、fileName() を呼び出してファイル名を取得し、QLabel に表示します。
  • ユーザーがファイルを選択して「開く」ボタンをクリックすると、選択されたファイルのフルパスが QString で返されます。
  • QFileDialog::getOpenFileName() を使用して、ファイル選択ダイアログを表示します。


QString の文字列操作関数を使用する

QFileInfo を使わずに、QString の文字列操作関数(lastIndexOf(), mid(), section() など)を使ってファイル名部分を抽出することができます。ただし、この方法はプラットフォーム間のパス区切り文字の違い(Windows の \ と Unix/Linux/macOS の /)を自分で処理する必要があるため、クロスプラットフォーム対応が必要な場合は推奨されません。


#include <QCoreApplication>
#include <QString>
#include <QDebug>

// パスからファイル名を抽出する関数(非常に単純化された例)
QString getFileNameManually(const QString &filePath) {
    int lastSlash = filePath.lastIndexOf('/');
    int lastBackslash = filePath.lastIndexOf('\\');

    int lastSeparator = qMax(lastSlash, lastBackslash); // どちらの区切り文字も考慮

    if (lastSeparator != -1) {
        // 区切り文字の次から最後までがファイル名
        return filePath.mid(lastSeparator + 1);
    } else {
        // 区切り文字がない場合は、パス全体がファイル名
        return filePath;
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString path1 = "/home/user/documents/report.pdf";
    qDebug() << "手動抽出 1: " << getFileNameManually(path1); // 出力: "report.pdf"

    QString path2 = "C:\\Users\\John\\Pictures\\photo.png";
    qDebug() << "手動抽出 2: " << getFileNameManually(path2); // 出力: "photo.png"

    QString path3 = "local_file.txt";
    qDebug() << "手動抽出 3: " << getFileNameManually(path3); // 出力: "local_file.txt"

    QString path4 = "/var/log/"; // ディレクトリの場合
    qDebug() << "手動抽出 4: " << getFileNameManually(path4); // 出力: "" (末尾スラッシュの処理がQFileInfoと異なる)

    // QFileInfoとの比較
    QFileInfo fi4(path4);
    qDebug() << "QFileInfo 4:   " << fi4.fileName(); // 出力: "log"

    return a.exec();
}

利点

  • 非常に単純なケースではコードが短くなる。
  • Qt の QFileInfo クラスに依存しない。

欠点

  • ディレクトリ名の抽出が異なる
    上記の例のように、ディレクトリパスの末尾にスラッシュがある場合、QFileInfo::fileName() はディレクトリ名を返しますが、手動の文字列操作では空文字列になることがあります。
  • エッジケースの処理が難しい
    末尾のスラッシュ、ドライブレター、UNC パス(\\server\share)、シンボリックリンク、特殊なファイルシステム上のパスなど、さまざまなエッジケースを考慮すると、非常に複雑でバグを起こしやすくなります。
  • クロスプラットフォーム性の欠如
    OS ごとのパス区切り文字(/\)の違いを自分で処理する必要があり、複雑になりがちです。

C++17 の std::filesystem を使用する

C++17 以降の標準ライブラリには、ファイルシステム操作のための std::filesystem が導入されました。これを使用すると、クロスプラットフォームかつ安全にファイルパスを操作できます。Qt に依存しないコードベースでファイル名抽出が必要な場合に非常に強力な選択肢です。


#include <iostream>
#include <string>
#include <filesystem> // C++17 から利用可能

namespace fs = std::filesystem; // 名前空間のエイリアス

int main()
{
    std::string path1_str = "/home/user/documents/report.pdf";
    fs::path path1 = path1_str;
    std::cout << "std::filesystem 1: " << path1.filename().string() << std::endl; // 出力: "report.pdf"

    std::string path2_str = "C:\\Users\\John\\Pictures\\photo.png";
    fs::path path2 = path2_str;
    std::cout << "std::filesystem 2: " << path2.filename().string() << std::endl; // 出力: "photo.png"

    std::string path3_str = "local_file.txt";
    fs::path path3 = path3_str;
    std::cout << "std::filesystem 3: " << path3.filename().string() << std::endl; // 出力: "local_file.txt"

    std::string path4_str = "/var/log/"; // ディレクトリの場合
    fs::path path4 = path4_str;
    std::cout << "std::filesystem 4 (ディレクトリ): " << path4.filename().string() << std::endl; // 出力: "log"

    std::string path5_str = "/"; // ルートディレクトリ
    fs::path path5 = path5_str;
    std::cout << "std::filesystem 5 (ルート): " << path5.filename().string() << std::endl; // 出力: ""

    std::string path6_str = "relative/path/to/folder";
    fs::path path6 = path6_str;
    std::cout << "std::filesystem 6 (フォルダ): " << path6.filename().string() << std::endl; // 出力: "folder"

    return 0;
}

利点

  • QFileInfo と同様の堅牢性
    QFileInfo と同様に、様々なエッジケース(末尾のスラッシュ、... など)を適切に処理します。
  • 高機能
    ファイル名だけでなく、親パス、拡張子、正規化されたパスなど、ファイルパスに関する様々な情報を取得できます。
  • クロスプラットフォーム
    OS ごとのパス区切り文字や規約を自動的に処理してくれます。
  • 標準ライブラリ
    C++ の標準機能であり、外部ライブラリに依存しません(C++17 以降)。

欠点

  • Qt の QString ではなく std::stringstd::wstring を扱うことになるため、Qt の他の部分とのデータ型の変換が必要になる場合があります。
  • C++17 以降が必要
    古い C++ 標準を使用しているプロジェクトでは利用できません。

プラットフォーム固有の API を使用する (非推奨)

Windows API の PathStripPath_splitpath_s、POSIX API の basename など、各 OS が提供するファイルパス操作関数を使用する方法です。

利点

  • Qt などの特定のフレームワークに依存しない、純粋な OS ネイティブコードになる。

欠点

  • Qt アプリケーションでは、通常、この方法は過剰な最適化や互換性のない要件がない限り、推奨されません。
  • 安全性や機能性の不足
    例えば basename はスレッドセーフでない可能性があったり、バッファオーバーフローに注意が必要だったりするなど、現代の C++ プログラミングには向かない点が多いです。
  • 深刻なクロスプラットフォーム性の欠如
    Windows と Unix 系 OS でコードを書き分ける必要があり、メンテナンスが非常に困難になります。

Qt アプリケーションでファイル名抽出を行う場合、QFileInfo::fileName() が最も推奨される方法です。その理由は、クロスプラットフォーム対応、堅牢なエッジケース処理、そして Qt の QString とのシームレスな統合にあります。

  • 低レベルな文字列操作やプラットフォーム固有 API
    特殊なケース(特定のパフォーマンス要件、非常に制約の厳しい環境など)を除き、一般的には避けるべきです。
  • Qt に依存しない C++ プロジェクト (C++17 以降)
    std::filesystem::path::filename() が優れた代替手段です。
  • Qt アプリケーション
    ほぼ常に QFileInfo::fileName() を使用すべきです。