閉じる

【C++】ファイルコピー|copy_fileの使い方と上書き・エラー処理

C++でファイルをコピーする際、かつてはstd::ifstreamstd::ofstreamを組み合わせてバッファを経由させるといった、やや手間のかかる実装が必要でした。

しかし、C++17からは標準ライブラリにfilesystemが追加され、std::filesystem::copy_fileという非常に便利な関数が利用可能になっています。

この関数を使うことで、OS固有のAPIを直接叩くことなく、シンプルかつ高速にファイルを複製できるようになりました。

本記事では、このcopy_fileの基本的な使い方から、上書き設定、エラーハンドリング、そして実務で役立つ詳細な仕様までを徹底的に解説します。

C++におけるファイルコピーの進化

C++17が登場するまで、標準ライブラリだけでファイルをコピーする公式な「1行の関数」は存在しませんでした。

開発者はBoostライブラリを使用するか、プラットフォームごとに異なるAPI(WindowsのCopyFileやPOSIXのsendfileなど)を使い分ける必要がありました。

C++17で導入されたstd::filesystem(ファイルシステムライブラリ)は、ファイルやディレクトリの操作をオブジェクト指向的かつ抽象的に扱うための仕組みを提供します。

その中でもcopy_fileは、ファイルコピーに特化した強力なツールとして位置づけられています。

copy_file関数の基本

std::filesystem::copy_fileは、指定されたコピー元(source)からコピー先(destination)へとファイルの内容を複製します。

この関数の最大の特徴は、単一のファイルのみを対象とする点にあります。

ディレクトリ全体をコピーしたい場合は、別の関数であるstd::filesystem::copyを使用することになりますが、ファイル単体の操作であればcopy_fileの方が軽量で意図が明確になります。

まずは、最もシンプルな実装例を見てみましょう。

C++
#include <iostream>
#include <filesystem> // filesystemライブラリをインクルード

namespace fs = std::filesystem; // 名前空間のエイリアスで記述を短縮

int main() {
    // コピー元とコピー先のパスを指定
    fs::path sourcePath = "example.txt";
    fs::path targetPath = "example_copy.txt";

    try {
        // 最もシンプルなファイルコピーの実行
        // デフォルトでは、コピー先が既に存在するとエラー(例外)が発生します
        bool result = fs::copy_file(sourcePath, targetPath);

        if (result) {
            std::cout << "ファイルのコピーに成功しました。" << std::endl;
        } else {
            std::cout << "コピーに失敗しました(ファイルが存在しない、またはその他の理由)。" << std::endl;
        }
    } catch (const fs::filesystem_error& e) {
        // エラーが発生した場合(ファイルが既に存在する、権限がない等)の処理
        std::cerr << "エラーが発生しました: " << e.what() << std::endl;
    }

    return 0;
}
実行結果
ファイルのコピーに成功しました。

このコードでは、example.txtの内容がそのままexample_copy.txtとして書き出されます。

非常に直感的ですが、実務では「ファイルが既に存在していたらどうするか」「上書きしたいのか、それともスキップしたいのか」といった詳細な制御が求められます。

copy_optionsによる動作のカスタマイズ

copy_fileの第3引数には、コピーの挙動を制御するためのオプションstd::filesystem::copy_optionsを渡すことができます。

これを指定しない場合はcopy_options::noneが適用され、「コピー先が既に存在すると例外を投げる」という挙動になります。

オプションの種類と解説

主要なオプションは以下の通りです。

これらはビットフラグとして定義されているため、論理和(|)で組み合わせることも可能ですが、copy_fileにおいては排他的に使用するのが一般的です。

オプション名内容
noneデフォルト。コピー先が存在する場合、エラーを発生させる。
skip_existingコピー先が既に存在する場合、何もせずにfalseを返す。エラーにはならない。
overwrite_existingコピー先が既に存在する場合、そのファイルを上書きする。
update_existingコピー先が既に存在し、かつコピー元の方が新しい場合のみ上書きする。

上書きコピーの実装例

ファイルを強制的に上書きしたい場合は、overwrite_existingを使用します。

これはバックアッププログラムなどを開発する際に頻繁に利用される設定です。

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

namespace fs = std::filesystem;

int main() {
    fs::path src = "data.log";
    fs::path dst = "backup/data_backup.log";

    // backupディレクトリが存在しない可能性を考慮して作成(任意)
    fs::create_directories(dst.parent_path());

    // 上書きオプションを指定してコピーを実行
    // std::error_codeを使用することで例外を発生させずにエラーを検知
    std::error_code ec;
    bool success = fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);

    if (ec) {
        std::cerr << "エラー: " << ec.message() << std::endl;
    } else if (success) {
        std::cout << "ファイルを上書きコピーしました。" << std::endl;
    }

    return 0;
}

更新分のみコピーする(update_existing)

update_existingは、タイムスタンプを比較してコピー元の方が新しい場合のみ処理を行います。

これにより、不要なディスク書き込みを減らし、効率的な同期処理が可能になります。

C++
// タイムスタンプに基づいた賢いコピー
fs::copy_file(src, dst, fs::copy_options::update_existing);

エラーハンドリングの使い分け

std::filesystemの多くの関数と同様に、copy_fileには2つのオーバーロードが存在します。

1. 例外(std::filesystem_error)を使用する方法

引数にパスだけを渡す形式です。

異常事態を例外として扱いたい場合に適しています。

  • メリット:エラーチェックを忘れることがなく、呼び出し元の深い場所で一括してエラーをキャッチできる。
  • デメリット:例外処理のオーバーヘッドが発生し、制御フローが複雑になることがある。

2. エラーコード(std::error_code)を使用する方法

第4引数(オプションを指定しない場合は第3引数)にstd::error_codeを渡す形式です。

  • メリット:例外を投げないため高速。ファイルが存在しないことが「想定内」である場合に使いやすい。
  • デメリット:戻り値やエラーコードのチェックをプログラマが明示的に行う必要がある。
C++
std::error_code ec;
fs::copy_file("source.txt", "dest.txt", ec);

if (ec) {
    // ec.value() でエラー番号、ec.message() でメッセージを取得可能
    std::cout << "エラーが発生しましたが、プログラムは継続します: " << ec.message() << std::endl;
}

copyとcopy_fileの決定的な違い

初心者の方が混同しやすいのが、std::filesystem::copystd::filesystem::copy_fileの違いです。

どちらもコピーを行いますが、その設計思想は大きく異なります。

copy_fileの特徴

  • 対象はファイルのみ
  • コピー先がディレクトリの場合、エラーになる(ディレクトリの中にコピーしてくれるわけではない)。
  • 挙動がシンプルで、ファイル単位の厳密な制御に向いている。

copyの特徴

  • ファイルだけでなく、ディレクトリも対象にできる
  • オプションを指定することで、ディレクトリを再帰的にコピー(recursive)することが可能。
  • 内部で対象がファイルかディレクトリかを判別するため、若干のオーバーヘッドがある。

「単一のファイルだと分かっている」のであれば、copy_fileを使うのが最も安全で意図の伝わりやすいコードになります。

実践的なTips:コピー失敗を防ぐために

ファイルをコピーする際には、コードレベルでの実装以外にも考慮すべき点がいくつかあります。

1. コピー先の親ディレクトリの確認

copy_fileは、コピー先のディレクトリ自体が存在しない場合、自動で作成してくれません

例えば、backup/2026/file.txtというパスにコピーしようとした際、backup/2026というフォルダが存在しないとエラーになります。

対策として、コピー前にstd::filesystem::create_directoriesを実行しておくのが定石です。

C++
fs::path dest = "path/to/backup/file.txt";
// 親ディレクトリを再帰的に作成(既に存在すれば何もしない)
fs::create_directories(dest.parent_path());
fs::copy_file(source, dest, fs::copy_options::overwrite_existing);

2. シンボリックリンクの扱い

copy_fileはデフォルトでシンボリックリンクを「追跡(デリファレンス)」します。

つまり、コピー元がリンクファイルであれば、そのリンク先の実体内容をコピー先に作成します。

リンクそのものをコピーしたい場合は、copy関数で適切なオプションを指定する必要があります。

3. パーミッション(権限)の継承

標準のcopy_fileは、ファイルの内容だけでなく属性(権限など)も可能な限りコピーしようと試みます。

しかし、OSやファイルシステム(NTFSからFAT32へのコピーなど)の制限により、完全に一致しない場合がある点に注意が必要です。

まとめ

C++17以降、std::filesystem::copy_fileの登場によって、ファイルコピーの実装は劇的に簡素化されました。

この関数を使いこなすためのポイントは以下の通りです。

  1. 基本動作:ファイルからファイルへの単純コピー。ディレクトリには使えない。
  2. 上書き制御copy_optionsを使って、上書き、スキップ、更新のみ、といった柔軟な設定を行う。
  3. 安全性:コピー先の親ディレクトリが存在するか、事前に確認または作成する。
  4. エラー対応:例外処理(try-catch)か、エラーコード(std::error_code)か、用途に応じて適切な方を選択する。

低レイヤーなストリーム操作を記述することなく、安全かつ効率的にファイルを扱えるようになったこの機能を、ぜひ皆さんのプロジェクトでも積極的に活用してみてください。

C++のモダンな書き方を取り入れることで、コードの可読性と保守性は確実に向上します。

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

URLをコピーしました!