C++でファイル操作を行う際、ファイルの移動や名前の変更は非常に頻繁に発生する処理です。
古くから使われているC言語由来の関数から、C++17で導入された強力なstd::filesystemライブラリまで、複数の方法が存在します。
この記事では、それぞれの使い分けや注意点、クロスプラットフォームでの挙動について詳しく解説します。
C++におけるファイル操作の進化
C++の世界において、ファイルの移動や名前の変更を行う方法は大きく分けて2つあります。
一つはC言語の標準ライブラリであるstdio.h(C++ではcstdio)に含まれるrename関数を使用する方法、もう一つはC++17から導入されたstd::filesystem::renameを使用する方法です。

かつてのC++開発では、OS固有のAPI(Windows APIやPOSIX)を直接叩くか、古いC関数を使い回すのが一般的でした。
しかし、現代のC++開発においては、コードの可読性や安全性の観点から「std::filesystem」の使用が推奨されています。
まずは、基本となる2つの手法の違いを理解することから始めましょう。
C言語由来のstd::rename関数
もっともシンプルで、古くから使われているのがstd::rename関数です。
この関数は非常に軽量であり、追加のライブラリをほとんど必要としません。
std::renameの基本的な使い方
この関数は、引数として「旧ファイル名」と「新ファイル名」を文字列(const char*)で受け取ります。
成功した場合は0を返し、失敗した場合は0以外の値を返します。
#include <iostream>
#include <cstdio> // std::renameを使用するために必要
int main() {
const char* old_name = "old_data.txt";
const char* new_name = "new_data.txt";
// ファイルの名前を変更(または移動)
if (std::rename(old_name, new_name) == 0) {
std::cout << "ファイル名の変更に成功しました。" << std::endl;
} else {
// 失敗した場合はエラーメッセージを表示
std::perror("エラーが発生しました");
}
return 0;
}
ファイル名の変更に成功しました。
(ファイルが存在しない場合)
エラーが発生しました: No such file or directory
std::renameのメリットとデメリット
std::renameの最大のメリットは、古い規格のC++(C++11やC++14以前)でも確実に動作するという点です。
また、標準的なC関数であるため、組み込み環境などリソースが限られた場所でも利用しやすいのが特徴です。
一方で、デメリットも少なくありません。
- エラーハンドリングが不十分
失敗した理由を知るためには、グローバル変数である
errnoをチェックする必要があります。- パスの扱いに弱い
単なる文字列としてパスを扱うため、ディレクトリの区切り文字(Windowsの「\」とLinuxの「/」)の差異をプログラマが意識しなければなりません。
- 移動の制限
異なるドライブやパーティションを跨ぐ移動ができない場合があります。
C++17のstd::filesystem::rename
現代的なC++開発であれば、std::filesystem::renameを使用するのがベストプラクティスです。
このライブラリは、ファイルパスそのものをオブジェクトとして扱うため、非常に直感的かつ安全に操作が可能です。

std::filesystemを用いた名前変更のコード
std::filesystemを使用する場合、std::pathオブジェクトを介して操作を行います。
これにより、文字列操作によるバグを大幅に減らすことができます。
#include <iostream>
#include <filesystem> // C++17以降が必要
namespace fs = std::filesystem;
int main() {
// パスオブジェクトの作成
fs::path source = "old_folder/data.bin";
fs::path target = "new_folder/data_backup.bin";
try {
// ファイルの移動(または名前変更)を実行
fs::rename(source, target);
std::cout << "移動が完了しました。" << std::endl;
} catch (const fs::filesystem_error& e) {
// 例外が発生した場合の処理
std::cerr << "エラー: " << e.what() << std::endl;
}
return 0;
}
移動が完了しました。
std::filesystem::renameの強力なポイント
この手法の素晴らしい点は、型安全性とプラットフォーム非依存性にあります。
例えば、Windows環境で「/」を使ってパスを記述しても、ライブラリ側で適切に解釈してくれます。
また、ファイルだけでなくディレクトリ(フォルダ)の移動も全く同じ構文で行えるのが大きな強みです。
さらに、例外処理(try-catch)を用いることで、何らかの理由でファイルがロックされていたり、権限が足りなかったりした場合のエラーハンドリングが非常に書きやすくなっています。
ファイル移動における注意点とトラブルシューティング
ファイルの移動や名前変更は、単純に見えて実は落とし穴が多い処理です。
特に実務で開発を行う際には、以下のポイントを考慮する必要があります。
異なるドライブ間の移動
もっとも注意すべきなのは、「rename」関数は物理的なドライブ(パーティション)を跨ぐ移動に失敗することがあるという点です。

多くのOSにおいて、renameはファイルシステム内のエントリを書き換えるだけの処理(ハードリンクの付け替えに近い動作)として実装されています。
そのため、物理的に異なるストレージへの移動(例:CドライブからUSBメモリへの移動)では、「Invalid cross-device link」というエラーが発生します。
これを解決するには、一度ファイルを「コピー」してから、元のファイルを「削除」するという手順を踏む必要があります。
std::filesystemを使えば、以下のように安全に実装できます。
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
void move_file_safe(const fs::path& from, const fs::path& to) {
try {
// まずはrenameを試みる(同じドライブ内なら高速)
fs::rename(from, to);
} catch (const fs::filesystem_error& e) {
// ドライブ跨ぎなどで失敗した場合、コピーして削除する
std::cout << "rename失敗。コピーによる移動を試みます..." << std::endl;
fs::copy(from, to);
fs::remove(from);
}
}
上書きの挙動
ファイル移動の際、移動先に同名のファイルが既に存在する場合の挙動は、OSや使用する関数によって異なります。
std::renameの場合:多くの環境で、既存のファイルは警告なしに上書きされます。std::filesystem::renameの場合:同様に上書きされることが一般的ですが、ディレクトリをファイル名で上書きしようとするなど、不適切な操作には厳格にエラーを返します。
不意なデータ消失を防ぐためには、移動前にfs::exists()を使って、移動先に同名ファイルがないかチェックする処理を入れるのが丁寧です。
エラーコードによる非例外的なハンドリング
例外処理(try-catch)を使いたくない、あるいはパフォーマンス上の理由で避けたい場合は、std::error_codeを受け取るオーバーロードを使用します。
これにより、プログラムのフローを止めずにエラーを判定できます。
#include <iostream>
#include <filesystem>
#include <system_error>
namespace fs = std::filesystem;
int main() {
fs::path src = "source.txt";
fs::path dst = "dest.txt";
std::error_code ec;
fs::rename(src, dst, ec); // 例外を投げないバージョン
if (ec) {
// エラー内容を表示
std::cerr << "エラー発生: " << ec.message() << std::endl;
} else {
std::cout << "成功しました。" << std::endl;
}
return 0;
}
各手法の比較まとめ
これまでに紹介した手法を、特徴ごとにまとめると以下のようになります。
| 特徴 | std::rename (C関数) | std::filesystem::rename (C++17) |
|---|---|---|
| 対応規格 | C++98 / 03 / 11 / 14 / 17以降 | C++17以降 |
| 引数の型 | const char* (文字列) | std::filesystem::path (オブジェクト) |
| エラー処理 | 戻り値と errno | 例外 または std::error_code |
| ディレクトリ操作 | OS依存だが可能 | 標準でサポート |
| 安全性 | 低い (パスの解釈がOS任せ) | 高い (パスを正規化して扱える) |
| 推奨度 | レガシー環境のみ推奨 | 現代の標準 |

実践的なディレクトリ移動の例
最後に、実際のアプリケーションでよくある「ログファイルを日付ごとのフォルダに整理して移動する」という処理を想定したサンプルコードを紹介します。
ここでは、移動先のディレクトリが存在しない場合に自動で作成する処理も含まれています。
#include <iostream>
#include <filesystem>
#include <string>
namespace fs = std::filesystem;
void archive_log_file(const std::string& filename, const std::string& date_str) {
fs::path current_log = filename;
fs::path archive_dir = "logs/" + date_str;
fs::path destination = archive_dir / filename;
try {
// 1. 保存先ディレクトリが存在するか確認し、なければ作成
if (!fs::exists(archive_dir)) {
fs::create_directories(archive_dir);
std::cout << "ディレクトリを作成しました: " << archive_dir << std::endl;
}
// 2. ファイルを移動
if (fs::exists(current_log)) {
fs::rename(current_log, destination);
std::cout << "ファイルをアーカイブしました: " << destination << std::endl;
} else {
std::cout << "ソースファイルが見つかりません。" << std::endl;
}
} catch (const fs::filesystem_error& e) {
std::cerr << "ファイル操作エラー: " << e.what() << std::endl;
}
}
int main() {
// 例: sample.log を logs/2026-01-20/ フォルダに移動
archive_log_file("sample.log", "2026-01-20");
return 0;
}
このコードのポイントは、fs::create_directoriesを使用している点です。
これにより、中間ディレクトリも含めて一気に作成できるため、移動処理の失敗を未然に防ぐことができます。
また、パスの結合に/演算子を使うことで、OSごとの区切り文字を気にする必要がなくなっています。
まとめ
C++でのファイル移動・名前変更は、かつての煩雑な文字列操作やOS依存のコードから、「std::filesystem」による安全で抽象化された操作へと進化しました。
古いC言語形式のstd::renameは、非常に限定的な状況(超軽量な組み込みシステムや古いコンパイラ環境)を除き、積極的に選択する理由は少なくなっています。
C++17以降が利用可能な環境であれば、型安全性に優れ、パス操作が容易なstd::filesystem::renameを使用しましょう。
ファイル操作は、ユーザーの大切なデータを扱う重要な処理です。
今回紹介した「ドライブ跨ぎの考慮」や「ディレクトリの事前作成」といったテクニックを組み合わせ、堅牢なプログラムを作成してください。
最新のC++機能を使いこなすことで、複雑なファイル管理も驚くほどシンプルに記述できるようになるはずです。
