C++でのファイル入力処理の基礎知識
ifstreamクラスの役割と基本概念
C++におけるifstream(input file stream)は、ファイルからデータを読み込むための標準的なクラスです。このクラスは、C++の入出力ストリームライブラリの一部として提供されており、<fstream>
ヘッダーに定義されています。
ifstreamの主な特徴:
- 継承関係
- ios_base → ios → istream → ifstreamという継承階層
- iostreamライブラリの一部として統一的に設計
- 他のストリーム操作と同じ感覚で使用可能
- オブジェクト指向的なアプローチ
- ファイルハンドルをクラスでカプセル化
- リソース管理の自動化(RAIIパターン)
- 型安全性の確保
- 豊富な入力操作
ifstream file("example.txt"); string line; int number; // 文字列の読み込み getline(file, line); // 数値の読み込み file >> number;
従来のC言語式ファイル処理との違い
C言語とC++のファイル処理には、以下のような重要な違いがあります:
項目 | C言語(FILE*) | C++(ifstream) |
---|---|---|
ファイルオープン | fopen関数 | コンストラクタまたはopen関数 |
ファイルクローズ | 明示的なfclose必要 | デストラクタで自動的に実行 |
エラー処理 | 戻り値のチェック | 例外機能とストリーム状態 |
型安全性 | 型指定が必要(fscanf) | ストリーム演算子で自動的に処理 |
C言語での実装例:
FILE* fp = fopen("data.txt", "r"); if (fp == NULL) { // エラー処理 return -1; } int value; fscanf(fp, "%d", &value); fclose(fp); // 明示的なクローズが必要
C++での実装例:
#include <fstream> #include <string> std::ifstream file("data.txt"); if (!file) { // エラー処理 return -1; } int value; file >> value; // ファイルは自動的にクローズ
ifstreamの利点:
- 安全性
- リソースリークの防止(RAIIパターン)
- 型安全な入力操作
- 例外による統一的なエラー処理
- 利便性
- ストリーム演算子によるシンプルな記述
- 文字列処理の容易さ
- 様々な型への自動変換
- 拡張性
- カスタム型への対応が容易
- 書式設定の柔軟性
- フィルタリングやマニピュレータの使用
このように、C++のifstreamは、C言語のファイル処理に比べて、より安全で使いやすい機能を提供します。次のセクションでは、これらの機能を実際に活用する方法について詳しく見ていきましょう。
ifstreamの基本的な使い方
ファイルを開いて読み込む基本的な手順
ifstreamを使用したファイル入力の基本的な手順を、実践的なコード例とともに解説します。
- ファイルのオープン
#include <fstream> #include <string> // 方法1: コンストラクタでオープン std::ifstream file1("input.txt"); // 方法2: open関数でオープン std::ifstream file2; file2.open("input.txt"); // バイナリモードでオープン std::ifstream binary_file("data.bin", std::ios::binary);
- データの読み込み方法
std::ifstream file("data.txt"); if (file) { // 方法1: ストリーム演算子を使用 int number; std::string text; file >> number >> text; // スペース区切りで読み込み // 方法2: getline関数を使用 std::string line; std::getline(file, line); // 1行全体を読み込み // 方法3: get関数で1文字読み込み char c; file.get(c); // 方法4: read関数でバイナリ読み込み char buffer[1024]; file.read(buffer, sizeof(buffer)); }
- 読み込み位置の制御
std::ifstream file("data.txt"); // 現在の位置を取得 std::streampos current = file.tellg(); // 特定の位置に移動 file.seekg(10); // 先頭から10バイト目に移動 file.seekg(-5, std::ios::end); // 末尾から5バイト前に移動 file.seekg(0, std::ios::beg); // 先頭に移動
ストリームの状態確認と例外処理
ファイル操作では適切な状態確認とエラー処理が重要です。
- ストリーム状態のチェック方法
状態フラグ | メソッド | 意味 |
---|---|---|
good() | file.good() | 全て正常 |
eof() | file.eof() | ファイル末尾に到達 |
fail() | file.fail() | 読み込み失敗 |
bad() | file.bad() | 致命的なエラー |
- 実践的なエラー処理の実装例
#include <fstream> #include <stdexcept> void readFile(const std::string& filename) { std::ifstream file(filename); // ファイルオープンの確認 if (!file) { throw std::runtime_error("Failed to open file: " + filename); } std::string line; while (std::getline(file, line)) { if (file.bad()) { // 致命的なエラー throw std::runtime_error("Fatal error while reading file"); } if (file.fail() && !file.eof()) { // 読み込み失敗(EOFは除く) file.clear(); // エラーフラグをクリア continue; } // 正常な行の処理 processLine(line); } }
- 推奨される例外安全なパターン
class FileReader { private: std::ifstream file; public: FileReader(const std::string& filename) : file(filename) { if (!file) { throw std::runtime_error("Failed to open file"); } } // ムーブコンストラクタのみを許可(コピー禁止) FileReader(FileReader&&) = default; FileReader& operator=(FileReader&&) = default; FileReader(const FileReader&) = delete; FileReader& operator=(const FileReader&) = delete; std::string readLine() { std::string line; if (std::getline(file, line)) { return line; } if (file.bad()) { throw std::runtime_error("Fatal error during read"); } return {}; // EOF or other error } // デストラクタでファイルは自動的にクローズされる };
このパターンは以下の利点があります:
- RAIIによるリソース管理
- 例外安全性の確保
- 使用方法の明確化
- エラー状態の適切な処理
次のセクションでは、より実践的なファイル入力処理のパターンについて詳しく見ていきます。
実践的なファイル入力処理の出現パターン
テキストファイルを1行ずつ読み込む方法
テキストファイルの行単位処理は最も一般的なユースケースです。以下に、効率的な実装パターンを示します。
- シンプルな行単位読み込み
#include <fstream> #include <string> #include <vector> std::vector<std::string> readLines(const std::string& filename) { std::ifstream file(filename); std::vector<std::string> lines; std::string line; // reserve()で再アロケーションを減らす lines.reserve(1000); // 予想される行数で初期化 while (std::getline(file, line)) { lines.push_back(std::move(line)); // moveで不要なコピーを防ぐ } return lines; }
- メモリ効率を考慮した行処理
void processLargeFile(const std::string& filename, std::function<void(const std::string&)> lineProcessor) { std::ifstream file(filename); std::string line; while (std::getline(file, line)) { lineProcessor(line); line.clear(); // メモリを解放 line.shrink_to_fit(); // キャパシティを最適化 } } // 使用例 processLargeFile("large.txt", [](const std::string& line) { // 行の処理 });
バイナリファイルの効率的な読み込み方法
バイナリデータの読み込みでは、バッファリングとメモリアライメントに注意が必要です。
- 構造体の読み込み
#pragma pack(push, 1) // アライメント制御 struct Record { int32_t id; char name[50]; double value; }; #pragma pack(pop) std::vector<Record> readRecords(const std::string& filename) { std::ifstream file(filename, std::ios::binary); std::vector<Record> records; // ファイルサイズを取得して必要なメモリを確保 file.seekg(0, std::ios::end); std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); size_t record_count = size / sizeof(Record); records.resize(record_count); // 一括読み込み file.read(reinterpret_cast<char*>(records.data()), record_count * sizeof(Record)); return records; }
- チャンク単位の読み込み
class ChunkReader { static constexpr size_t CHUNK_SIZE = 8192; // 8KB chunks std::ifstream file; std::vector<char> buffer; public: ChunkReader(const std::string& filename) : file(filename, std::ios::binary), buffer(CHUNK_SIZE) {} size_t readNextChunk() { file.read(buffer.data(), CHUNK_SIZE); return file.gcount(); // 実際に読み込んだバイト数 } const char* data() const { return buffer.data(); } };
大容量ファイルを扱う際の最適化テクニック
大容量ファイルの処理では、メモリ使用量とパフォーマンスのバランスが重要です。
- メモリマッピングの活用
#include <fstream> #include <sys/mman.h> #include <fcntl.h> #include <unistd.h> class MemoryMappedFile { void* mapped_data; size_t file_size; int fd; public: MemoryMappedFile(const std::string& filename) { fd = open(filename.c_str(), O_RDONLY); if (fd == -1) throw std::runtime_error("Failed to open file"); file_size = lseek(fd, 0, SEEK_END); mapped_data = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mapped_data == MAP_FAILED) { close(fd); throw std::runtime_error("Failed to map file"); } } ~MemoryMappedFile() { munmap(mapped_data, file_size); close(fd); } const char* data() const { return static_cast<char*>(mapped_data); } size_t size() const { return file_size; } };
- ストリーミング処理パターン
template<typename T> class StreamProcessor { std::ifstream file; static constexpr size_t BUFFER_SIZE = 4096; std::vector<char> buffer; public: StreamProcessor(const std::string& filename) : file(filename, std::ios::binary), buffer(BUFFER_SIZE) {} template<typename Processor> void process(Processor proc) { while (file) { file.read(buffer.data(), BUFFER_SIZE); std::streamsize count = file.gcount(); if (count > 0) { proc(buffer.data(), count); } } } }; // 使用例 StreamProcessor<char> processor("large_file.dat"); size_t total_bytes = 0; processor.process([&total_bytes](const char* data, size_t size) { total_bytes += size; // データの処理 });
これらのパターンは、実際の開発現場で頻繁に使用される実装例です。次のセクションでは、これらの実装におけるエラーハンドリングのベストプラクティスについて解説します。
エラーハンドリングのベストプラクティス
よくあるエラーパターンとその対処法
ファイル入力処理で発生する主なエラーパターンとその適切な対処方法を解説します。
- ファイルオープンエラーの処理
#include <system_error> class FileOpenError : public std::runtime_error { public: explicit FileOpenError(const std::string& filename) : std::runtime_error("Failed to open file: " + filename) {} }; class SafeFileReader { private: std::ifstream file; std::string filename; public: explicit SafeFileReader(const std::string& fname) : filename(fname) { file.open(filename); if (!file) { // システムエラー情報を含めたエラー処理 std::error_code ec(errno, std::system_category()); throw FileOpenError(filename + ": " + ec.message()); } } };
- 読み込みエラーの検出と処理
enum class ReadError { EOF_REACHED, INVALID_FORMAT, IO_ERROR }; template<typename T> class ResultOr { std::variant<T, ReadError> result; public: explicit ResultOr(T value) : result(std::move(value)) {} explicit ResultOr(ReadError error) : result(error) {} bool hasValue() const { return std::holds_alternative<T>(result); } const T& value() const { return std::get<T>(result); } ReadError error() const { return std::get<ReadError>(result); } }; ResultOr<std::string> readLine(std::ifstream& file) { std::string line; if (file.eof()) { return ResultOr<std::string>(ReadError::EOF_REACHED); } if (file.fail()) { return ResultOr<std::string>(ReadError::INVALID_FORMAT); } if (file.bad()) { return ResultOr<std::string>(ReadError::IO_ERROR); } std::getline(file, line); return ResultOr<std::string>(std::move(line)); }
例外安全なファイル処理の実装方法
- 強い例外保証を提供するクラス設計
class FileProcessor { private: std::ifstream file; std::vector<char> buffer; static constexpr size_t BUFFER_SIZE = 4096; public: FileProcessor(const std::string& filename) try : file(filename, std::ios::binary), buffer(BUFFER_SIZE) { // 初期化が成功した場合のみここに到達 } catch (...) { // 初期化中に例外が発生した場合の後処理 if (file.is_open()) { file.close(); } throw; // 例外を再スロー } // ムーブ可能だがコピー不可能に設定 FileProcessor(FileProcessor&&) noexcept = default; FileProcessor& operator=(FileProcessor&&) noexcept = default; FileProcessor(const FileProcessor&) = delete; FileProcessor& operator=(const FileProcessor&) = delete; template<typename Handler> void processFile(Handler&& handler) { try { while (processNextChunk(std::forward<Handler>(handler))) {} } catch (...) { // エラー状態をクリアして再スロー file.clear(); throw; } } private: template<typename Handler> bool processNextChunk(Handler&& handler) { file.read(buffer.data(), BUFFER_SIZE); std::streamsize count = file.gcount(); if (count > 0) { handler(buffer.data(), count); return true; } return false; } };
- トランザクション的なファイル処理
class TransactionalFileProcessor { private: std::string filename; std::vector<std::string> processed_lines; public: explicit TransactionalFileProcessor(std::string fname) : filename(std::move(fname)) {} template<typename Processor> void process(Processor&& line_processor) { std::ifstream file(filename); if (!file) { throw FileOpenError(filename); } std::string line; processed_lines.clear(); try { while (std::getline(file, line)) { auto processed = line_processor(line); processed_lines.push_back(std::move(processed)); } } catch (...) { // エラー発生時はすべての処理をロールバック processed_lines.clear(); throw; } } // 処理結果の取得 const std::vector<std::string>& getResults() const { return processed_lines; } };
エラーハンドリングにおける重要なポイント:
- 階層的なエラー処理
- システムレベルのエラー(ファイルシステム関連)
- フォーマットエラー(データ形式不正)
- 論理エラー(アプリケーション固有の制約違反)
- エラー情報の適切な伝播
- 意味のある例外クラスの設計
- エラーコンテキストの保持
- 適切なエラーメッセージの提供
- リソースの確実な解放
- RAIIパターンの徹底
- スマートポインタの活用
- 例外安全性の確保
このようなエラーハンドリングパターンを適切に実装することで、堅牢なファイル処理システムを構築できます。次のセクションでは、ifstreamを使用する際の具体的な注意点について解説します。
ifstreamを使用する際の注意点
メモリリークを防ぐためのRAIIパターンの活用
ifstreamの使用時におけるメモリリークの防止は、適切なリソース管理の基本です。
- RAIIパターンの実装例
class FileGuard { private: std::ifstream file; public: explicit FileGuard(const std::string& filename) : file(filename) { if (!file) { throw std::runtime_error("Failed to open file: " + filename); } } // ムーブセマンティクスの実装 FileGuard(FileGuard&& other) noexcept = default; FileGuard& operator=(FileGuard&& other) noexcept = default; // コピーの禁止 FileGuard(const FileGuard&) = delete; FileGuard& operator=(const FileGuard&) = delete; // ファイルストリームへのアクセス std::ifstream& get() { return file; } const std::ifstream& get() const { return file; } // デストラクタでの自動クローズ ~FileGuard() { if (file.is_open()) { file.close(); } } };
- スコープベースのリソース管理
void processFile(const std::string& filename) { // スコープを抜けると自動的にファイルがクローズされる FileGuard guard(filename); std::string line; while (std::getline(guard.get(), line)) { // 行の処理 } // 例外が発生しても、guardのデストラクタが呼ばれる }
マルチスレッド環境での安全な使用方法
マルチスレッド環境でのファイル操作には特別な注意が必要です。
- スレッドセーフな実装パターン
class ThreadSafeFileReader { private: std::mutex mutex; std::ifstream file; public: explicit ThreadSafeFileReader(const std::string& filename) : file(filename) { if (!file) { throw std::runtime_error("File open failed"); } } // スレッドセーフな読み込み操作 std::optional<std::string> readLine() { std::lock_guard<std::mutex> lock(mutex); std::string line; if (std::getline(file, line)) { return line; } return std::nullopt; } // バッファ付き読み込み std::vector<std::string> readLines(size_t count) { std::lock_guard<std::mutex> lock(mutex); std::vector<std::string> lines; lines.reserve(count); std::string line; while (lines.size() < count && std::getline(file, line)) { lines.push_back(std::move(line)); } return lines; } };
- 並行処理のための設計パターン
class FileProcessor { private: ThreadSafeFileReader reader; std::vector<std::thread> workers; std::atomic<bool> stop_flag{false}; public: explicit FileProcessor(const std::string& filename) : reader(filename) {} void processInParallel(size_t thread_count) { for (size_t i = 0; i < thread_count; ++i) { workers.emplace_back([this]() { while (!stop_flag) { if (auto line = reader.readLine()) { processLine(*line); } else { break; } } }); } } void stop() { stop_flag = true; for (auto& worker : workers) { if (worker.joinable()) { worker.join(); } } } private: void processLine(const std::string& line) { // 行の処理 } };
重要な注意点と対策
- ファイルディスクリプタの制限
class FileDescriptorManager { private: static std::atomic<int> open_files; static const int MAX_OPEN_FILES = 1000; // システム依存 public: static bool canOpenFile() { return open_files < MAX_OPEN_FILES; } static void registerFile() { ++open_files; } static void unregisterFile() { --open_files; } }; class ManagedFileStream { private: std::ifstream file; public: explicit ManagedFileStream(const std::string& filename) { if (!FileDescriptorManager::canOpenFile()) { throw std::runtime_error("Too many open files"); } file.open(filename); if (file.is_open()) { FileDescriptorManager::registerFile(); } } ~ManagedFileStream() { if (file.is_open()) { file.close(); FileDescriptorManager::unregisterFile(); } } };
- バッファリングに関する注意点
- デフォルトのバッファサイズが不適切な場合がある
- 大容量ファイルではカスタムバッファリングを検討
- メモリ使用量とパフォーマンスのトレードオフを考慮
class CustomBufferedStream { private: std::ifstream file; std::vector<char> buffer; public: CustomBufferedStream(const std::string& filename, size_t buffer_size) : file(filename), buffer(buffer_size) { // カスタムバッファの設定 file.rdbuf()->pubsetbuf(buffer.data(), buffer.size()); } };
これらの注意点を適切に考慮することで、より安全で効率的なファイル処理を実現できます。次のセクションでは、具体的なパフォーマンスチューニングの方法について解説します。
パフォーマンスチューニングのポイント
バッファリングの最適化による読み込み速度の向上
ファイル入力処理のパフォーマンスを向上させるための効果的なバッファリング手法を解説します。
- カスタムバッファサイズの設定
#include <chrono> class OptimizedFileReader { private: std::ifstream file; std::vector<char> buffer; static constexpr size_t determineOptimalBufferSize() { // ページサイズの倍数を使用 return 4096 * 16; // 64KB } public: explicit OptimizedFileReader(const std::string& filename) : file(filename), buffer(determineOptimalBufferSize()) { // カスタムバッファの設定 file.rdbuf()->pubsetbuf(buffer.data(), buffer.size()); } // パフォーマンス計測付きの読み込み template<typename Processor> auto processWithTiming(Processor&& proc) { using namespace std::chrono; auto start = high_resolution_clock::now(); std::string line; while (std::getline(file, line)) { proc(line); } auto end = high_resolution_clock::now(); return duration_cast<milliseconds>(end - start); } };
- メモリマッピングを活用した高速化
#include <sys/mman.h> #include <fcntl.h> #include <unistd.h> class MemoryMappedReader { private: int fd; void* mapped_data; size_t file_size; // ページサイズ単位でのアライメント static size_t alignToPage(size_t size) { static const size_t page_size = sysconf(_SC_PAGESIZE); return (size + page_size - 1) & ~(page_size - 1); } public: explicit MemoryMappedReader(const std::string& filename) { fd = open(filename.c_str(), O_RDONLY); if (fd == -1) { throw std::runtime_error("Failed to open file"); } file_size = lseek(fd, 0, SEEK_END); file_size = alignToPage(file_size); mapped_data = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0); if (mapped_data == MAP_FAILED) { close(fd); throw std::runtime_error("Failed to map file"); } // プリフェッチのヒント madvise(mapped_data, file_size, MADV_SEQUENTIAL); } ~MemoryMappedReader() { if (mapped_data != MAP_FAILED) { munmap(mapped_data, file_size); } if (fd != -1) { close(fd); } } template<typename Processor> void processLines(Processor&& proc) { const char* current = static_cast<const char*>(mapped_data); const char* end = current + file_size; while (current < end) { const char* line_end = std::find(current, end, '\n'); proc(std::string_view(current, line_end - current)); current = line_end + 1; } } };
メモリ使用量の削減テクニック
- ストリーミング処理による最適化
class StreamingProcessor { private: static constexpr size_t CHUNK_SIZE = 16384; // 16KB chunks std::vector<char> buffer; public: StreamingProcessor() : buffer(CHUNK_SIZE) {} template<typename Processor> void processLargeFile(const std::string& filename, Processor&& proc) { std::ifstream file(filename, std::ios::binary); while (file) { file.read(buffer.data(), buffer.size()); std::streamsize count = file.gcount(); if (count > 0) { proc(std::string_view(buffer.data(), count)); } } } };
- メモリプール活用による最適化
template<size_t BlockSize = 4096> class StringPool { private: std::vector<std::array<char, BlockSize>> blocks; size_t current_block = 0; size_t current_pos = 0; public: std::string_view allocateString(const std::string& str) { if (str.length() > BlockSize) { throw std::runtime_error("String too large for pool"); } if (current_pos + str.length() > BlockSize) { current_block++; current_pos = 0; } if (current_block >= blocks.size()) { blocks.emplace_back(); } auto& block = blocks[current_block]; std::copy(str.begin(), str.end(), block.begin() + current_pos); std::string_view result(block.data() + current_pos, str.length()); current_pos += str.length(); return result; } }; class OptimizedLineProcessor { private: StringPool<> pool; std::vector<std::string_view> lines; public: void processFile(const std::string& filename) { std::ifstream file(filename); std::string line; while (std::getline(file, line)) { lines.push_back(pool.allocateString(line)); } } };
パフォーマンス最適化のベストプラクティス
- 測定に基づく最適化
class PerformanceMetrics { public: template<typename Func> static auto measureExecutionTime(Func&& func) { using namespace std::chrono; auto start = high_resolution_clock::now(); func(); auto end = high_resolution_clock::now(); return duration_cast<milliseconds>(end - start); } template<typename Func> static void compareImplementations( const std::string& filename, const std::vector<std::pair<std::string, Func>>& impls) { std::cout << "Performance comparison:\n"; for (const auto& [name, impl] : impls) { auto duration = measureExecutionTime([&]() { impl(filename); }); std::cout << name << ": " << duration.count() << "ms\n"; } } };
- システムリソースの考慮
class SystemAwareReader { private: static size_t getOptimalBufferSize() { // システムのメモリ状態を考慮 std::ifstream meminfo("/proc/meminfo"); size_t available_kb = 0; std::string line; while (std::getline(meminfo, line)) { if (line.starts_with("MemAvailable:")) { std::istringstream iss(line); std::string dummy; iss >> dummy >> available_kb; break; } } // 利用可能メモリの1%を上限とする return std::min<size_t>( available_kb * 1024 / 100, // 1% 16 * 1024 * 1024 // 最大16MB ); } public: static void configureForSystem(std::ifstream& file) { static const size_t buffer_size = getOptimalBufferSize(); static std::vector<char> buffer(buffer_size); file.rdbuf()->pubsetbuf(buffer.data(), buffer.size()); } };
これらの最適化テクニックを適切に組み合わせることで、効率的なファイル処理システムを構築できます。次のセクションでは、これらの知識を活用した実践的なコード例を紹介します。
実践的なコード例と解説
CSVファイルの効率的な読み込み実装
テキストファイルの中でも特に頻出するCSVファイルの処理について、実用的な実装例を示します。
#include <fstream> #include <vector> #include <string> #include <sstream> #include <stdexcept> #include <memory> // CSVレコードを表現するクラス class CSVRecord { private: std::vector<std::string> fields; const std::vector<std::string>& headers; public: CSVRecord(std::vector<std::string> fields_, const std::vector<std::string>& headers_) : fields(std::move(fields_)), headers(headers_) { if (fields.size() != headers.size()) { throw std::runtime_error("Field count does not match header count"); } } // フィールド名で値を取得 const std::string& getField(const std::string& fieldName) const { auto it = std::find(headers.begin(), headers.end(), fieldName); if (it == headers.end()) { throw std::runtime_error("Field not found: " + fieldName); } return fields[std::distance(headers.begin(), it)]; } // インデックスで値を取得 const std::string& operator[](size_t index) const { return fields.at(index); } }; class CSVReader { private: std::ifstream file; char delimiter; std::vector<std::string> headers; // 1行をフィールドに分割 static std::vector<std::string> splitLine(const std::string& line, char delim) { std::vector<std::string> fields; std::stringstream ss(line); std::string field; // ダブルクォートとカンマを適切に処理 bool in_quotes = false; std::string current_field; for (char ch : line) { if (ch == '"') { in_quotes = !in_quotes; } else if (ch == delim && !in_quotes) { fields.push_back(trim(current_field)); current_field.clear(); } else { current_field += ch; } } fields.push_back(trim(current_field)); return fields; } // 文字列の前後の空白を削除 static std::string trim(const std::string& str) { const auto begin = str.find_first_not_of(" \t\r\n\""); if (begin == std::string::npos) return ""; const auto end = str.find_last_not_of(" \t\r\n\""); return str.substr(begin, end - begin + 1); } public: CSVReader(const std::string& filename, char delim = ',') : file(filename), delimiter(delim) { if (!file) { throw std::runtime_error("Failed to open file: " + filename); } // ヘッダー行の読み込み std::string header_line; if (std::getline(file, header_line)) { headers = splitLine(header_line, delimiter); } else { throw std::runtime_error("Empty CSV file"); } } // 1レコードずつ処理するイテレータ class iterator { private: CSVReader* reader; std::unique_ptr<CSVRecord> current_record; public: explicit iterator(CSVReader* r = nullptr) : reader(r) { if (reader) { readNext(); } } iterator& operator++() { readNext(); return *this; } bool operator!=(const iterator& other) const { return reader != other.reader; } CSVRecord& operator*() { return *current_record; } private: void readNext() { std::string line; if (std::getline(reader->file, line)) { auto fields = reader->splitLine(line, reader->delimiter); current_record = std::make_unique<CSVRecord>(std::move(fields), reader->headers); } else { reader = nullptr; } } }; iterator begin() { return iterator(this); } iterator end() { return iterator(); } }; // 使用例:売上データの集計 void analyzeSalesData(const std::string& filename) { try { CSVReader csv(filename); double total_revenue = 0.0; size_t transaction_count = 0; // イテレータを使用して各レコードを処理 for (const auto& record : csv) { try { // 金額フィールドを数値に変換して集計 double amount = std::stod(record.getField("Amount")); total_revenue += amount; transaction_count++; } catch (const std::exception& e) { std::cerr << "Error processing record: " << e.what() << std::endl; } } // 結果の出力 if (transaction_count > 0) { std::cout << "Total Revenue: " << total_revenue << "\n" << "Transaction Count: " << transaction_count << "\n" << "Average Transaction: " << (total_revenue / transaction_count) << std::endl; } } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } }
設定ファイルのパース処理の実装例
アプリケーションの設定ファイルを読み込む実装例を示します。
#include <fstream> #include <unordered_map> #include <optional> #include <variant> class ConfigReader { public: using ValueType = std::variant<std::string, int, double, bool>; private: struct Section { std::unordered_map<std::string, ValueType> values; }; std::unordered_map<std::string, Section> sections; static constexpr size_t MAX_LINE_LENGTH = 1024; static std::pair<std::string, std::string> parseKeyValue(const std::string& line) { size_t pos = line.find('='); if (pos == std::string::npos) { throw std::runtime_error("Invalid configuration line: " + line); } return { trim(line.substr(0, pos)), trim(line.substr(pos + 1)) }; } static std::string trim(const std::string& str) { const auto begin = str.find_first_not_of(" \t\r\n"); if (begin == std::string::npos) return ""; const auto end = str.find_last_not_of(" \t\r\n"); return str.substr(begin, end - begin + 1); } // 文字列値を適切な型に変換 static ValueType parseValue(const std::string& value) { // 数値として解釈を試みる try { size_t pos; int int_value = std::stoi(value, &pos); if (pos == value.length()) { return int_value; } double double_value = std::stod(value, &pos); if (pos == value.length()) { return double_value; } } catch (...) { // 数値変換に失敗した場合は続行 } // 真偽値として解釈を試みる std::string lower_value = value; std::transform(lower_value.begin(), lower_value.end(), lower_value.begin(), ::tolower); if (lower_value == "true" || lower_value == "yes" || lower_value == "on" || lower_value == "1") { return true; } if (lower_value == "false" || lower_value == "no" || lower_value == "off" || lower_value == "0") { return false; } // それ以外は文字列として扱う return value; } public: explicit ConfigReader(const std::string& filename) { std::ifstream file(filename); if (!file) { throw std::runtime_error("Failed to open configuration file: " + filename); } std::string current_section; std::string line; while (std::getline(file, line)) { line = trim(line); if (line.empty() || line[0] == '#' || line[0] == ';') { continue; // コメントまたは空行をスキップ } if (line[0] == '[' && line.back() == ']') { // セクション行の処理 current_section = line.substr(1, line.length() - 2); } else { // キー=値の行の処理 auto [key, value] = parseKeyValue(line); sections[current_section].values[key] = parseValue(value); } } } // 型安全な値の取得 template<typename T> std::optional<T> getValue(const std::string& section, const std::string& key) const { auto sect_it = sections.find(section); if (sect_it == sections.end()) { return std::nullopt; } auto value_it = sect_it->second.values.find(key); if (value_it == sect_it->second.values.end()) { return std::nullopt; } try { return std::get<T>(value_it->second); } catch (const std::bad_variant_access&) { return std::nullopt; } } }; // 使用例 int main() { try { ConfigReader config("config.ini"); // データベース設定の読み込み if (auto host = config.getValue<std::string>("Database", "host")) { std::cout << "Database host: " << *host << std::endl; } if (auto port = config.getValue<int>("Database", "port")) { std::cout << "Database port: " << *port << std::endl; } if (auto use_ssl = config.getValue<bool>("Database", "ssl")) { std::cout << "Use SSL: " << std::boolalpha << *use_ssl << std::endl; } } catch (const std::exception& e) { std::cerr << "Configuration error: " << e.what() << std::endl; return 1; } return 0; }
これらの実装例の特徴:
- 型安全性
- 適切な型チェックと変換
- std::variantを使用した柔軟な値の保持
- エラー時の例外処理
- メモリ効率
- 必要最小限のメモリ使用
- 文字列のコピーを最小限に抑制
- スマートポインタの活用
- エラー処理
- 適切な例外の使用
- エラー状態の詳細な報告
- 復帰可能なエラーの処理
- 拡張性
- モジュラーな設計
- カスタマイズ可能なインターフェース
- 将来の機能追加を考慮した構造
これらのコード例は、実務での使用に耐える堅牢性と効率性を備えており、必要に応じて拡張することも容易です。