std::getlineとは – 基礎から応用まで完全解説
文字列入力の新標準 – std::getlineが選ばれる理由
std::getlineは、C++標準ライブラリが提供する文字列入力のための関数です。従来のC言語スタイルの入力方法と比較して、以下のような優位点を持っています:
- 型安全性: バッファオーバーフローのリスクを低減
- 柔軟な区切り文字: デフォルトの改行文字以外も指定可能
- 文字列全体の取得: スペースを含む文字列も一度に読み取り可能
- 例外処理: 入力エラーを適切に検出・処理可能
std::getlineの基本構文と動作原理
std::getlineの基本的な構文は以下の2つの形式があります:
// 形式1: 文字列を受け取る基本形 std::getline(std::istream& input, std::string& str); // 形式2: 区切り文字を指定する拡張形 std::getline(std::istream& input, std::string& str, char delim);
以下は、基本的な使用例です:
#include <iostream>
#include <string>
int main() {
std::string input_line;
// 標準入力から1行読み込み
std::getline(std::cin, input_line);
// カンマを区切り文字として指定した読み込み
std::getline(std::cin, input_line, ',');
return 0;
}
動作の仕組み
- 入力ストリームの読み取り
- 指定された入力ストリーム(std::cinなど)からデータを1文字ずつ読み取ります
- 読み取られた文字は内部バッファに蓄積されます
- 区切り文字の検出
- デフォルトでは改行文字(\n)が区切り文字として使用されます
- 区切り文字が見つかるまで読み取りを継続します
- 文字列の格納
- 読み取られた文字列は指定されたstd::string変数に格納されます
- 区切り文字自体は結果の文字列には含まれません
戻り値と状態フラグ
getlineは入力ストリームへの参照を返し、以下の状態を確認できます:
std::string line;
if (std::getline(std::cin, line)) {
// 読み取り成功
} else {
// 読み取り失敗(EOFまたはエラー)
}
主な状態フラグ:
- good(): エラーが発生していない
- eof(): ファイル終端に達した
- fail(): 読み取り操作が失敗した
- bad(): 深刻なエラーが発生した
他の入力方法との比較
| 機能 | std::getline | std::cin >> | gets() |
|---|---|---|---|
| バッファオーバーフロー対策 | ✅ | ✅ | ❌ |
| スペースを含む文字列の読み取り | ✅ | ❌ | ✅ |
| カスタム区切り文字 | ✅ | ❌ | ❌ |
| 型安全性 | ✅ | ✅ | ❌ |
| 例外処理のサポート | ✅ | ✅ | ❌ |
ベストプラクティス
- 入力バッファのクリア
// 数値入力後の改行文字を消費 int number; std::cin >> number; std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); std::string line; std::getline(std::cin, line);
- エラー処理の実装
std::string line;
while (std::getline(std::cin, line)) {
try {
// 入力処理
} catch (const std::exception& e) {
std::cerr << "エラー: " << e.what() << std::endl;
continue;
}
}
- 空行のチェック
std::string line;
if (std::getline(std::cin, line) && !line.empty()) {
// 非空行の処理
}
これらの基本的な使い方を理解することで、より高度な使用方法やエラー処理、最適化テクニックの習得へとつながります。
std::getline の実践的な使用方法
ファイル入力における効率的なデータ読み取り方法
ファイル処理は実務で頻繁に発生するタスクです。std::getlineを使用することで、効率的かつ安全にファイルを読み取ることができます。
基本的なファイル読み取り
#include <fstream>
#include <string>
#include <iostream>
void readFile(const std::string& filename) {
std::ifstream file(filename);
if (!file) {
throw std::runtime_error("ファイルを開けませんでした: " + filename);
}
std::string line;
while (std::getline(file, line)) {
// 各行の処理
std::cout << "Read: " << line << std::endl;
}
}
大容量ファイルの効率的な読み取り
#include <fstream>
#include <string>
#include <vector>
std::vector<std::string> readLargeFile(const std::string& filename) {
std::vector<std::string> lines;
std::ifstream file(filename);
// バッファサイズの最適化
file.rdbuf()->pubsetbuf(nullptr, 0);
// 予約による再アロケーション回数の削減
lines.reserve(1000); // 予想される行数に応じて調整
std::string line;
while (std::getline(file, line)) {
lines.emplace_back(std::move(line)); // moveによるコピーコストの削減
}
return lines;
}
ユーザー入力を安全に処理するベストプラクティス
ユーザー入力の処理は、セキュリティとユーザビリティの両面で重要です。以下に、安全な入力処理の実装例を示します。
入力バリデーション付きの文字列取得
#include <string>
#include <iostream>
#include <limits>
std::string getValidatedInput(const std::string& prompt,
size_t minLength = 0,
size_t maxLength = std::numeric_limits<size_t>::max()) {
std::string input;
bool isValid = false;
while (!isValid) {
std::cout << prompt;
if (std::getline(std::cin, input)) {
if (input.length() >= minLength && input.length() <= maxLength) {
isValid = true;
} else {
std::cout << "入力は" << minLength << "から" << maxLength
<< "文字の間である必要があります。" << std::endl;
}
} else {
std::cin.clear(); // エラーフラグのクリア
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "無効な入力です。再度試してください。" << std::endl;
}
}
return input;
}
区切り文字を活用したCSVパーサーの実装例
CSVファイルの処理は一般的なタスクですが、適切な実装が重要です。
シンプルなCSVパーサー
#include <vector>
#include <sstream>
class CSVParser {
private:
char delimiter;
public:
explicit CSVParser(char delim = ',') : delimiter(delim) {}
std::vector<std::string> parseLine(const std::string& line) {
std::vector<std::string> fields;
std::stringstream ss(line);
std::string field;
// 区切り文字でフィールドを分割
while (std::getline(ss, field, delimiter)) {
// 前後の空白を削除
field.erase(0, field.find_first_not_of(" \t"));
field.erase(field.find_last_not_of(" \t") + 1);
fields.push_back(field);
}
return fields;
}
std::vector<std::vector<std::string>> parseFile(const std::string& filename) {
std::vector<std::vector<std::string>> data;
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
if (!line.empty()) {
data.push_back(parseLine(line));
}
}
return data;
}
};
高度なCSV処理の実装例
#include <optional>
class AdvancedCSVParser {
public:
struct ParseOptions {
char delimiter = ',';
bool skipEmptyLines = true;
bool trimFields = true;
std::optional<char> quoteChar = '"';
};
static std::vector<std::string> parseLine(
const std::string& line,
const ParseOptions& options = ParseOptions()) {
std::vector<std::string> fields;
std::string field;
bool inQuotes = false;
for (size_t i = 0; i < line.length(); ++i) {
char c = line[i];
if (options.quoteChar && c == options.quoteChar.value()) {
inQuotes = !inQuotes;
} else if (c == options.delimiter && !inQuotes) {
if (options.trimFields) {
trimString(field);
}
fields.push_back(field);
field.clear();
} else {
field += c;
}
}
// 最後のフィールドの処理
if (options.trimFields) {
trimString(field);
}
fields.push_back(field);
return fields;
}
private:
static void trimString(std::string& str) {
const std::string whitespace = " \t\r\n";
str.erase(0, str.find_first_not_of(whitespace));
str.erase(str.find_last_not_of(whitespace) + 1);
}
};
これらの実装例は、実際の開発現場で直面する様々な要件に対応できるよう設計されています。特に以下の点に注意を払っています:
- エラー処理の適切な実装
- メモリ使用の最適化
- パフォーマンスへの配慮
- コードの再利用性
- 拡張性の確保
次のセクションでは、これらの実装を行う際に遭遇する可能性のある一般的なエラーとその解決方法について説明します。
よくあるエラーとトラブルシューティング
入力バッファの残留データによる誤動作の解決法
問題1: 数値入力後の getline が空行を返す
#include <iostream>
#include <string>
void demonstrateBufferProblem() {
int number;
std::string line;
std::cout << "数字を入力: ";
std::cin >> number; // 例: "123\n" を入力
std::cout << "文字列を入力: ";
std::getline(std::cin, line); // 直前の改行文字を読み取って終了
std::cout << "入力された文字列: [" << line << "]" << std::endl; // 空文字列が出力される
}
解決策1: ignore() による入力バッファのクリア
void solveBufferProblem() {
int number;
std::string line;
std::cout << "数字を入力: ";
std::cin >> number;
// 入力バッファから残りの文字を削除
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "文字列を入力: ";
std::getline(std::cin, line);
std::cout << "入力された文字列: [" << line << "]" << std::endl;
}
問題2: 混在する入力形式の処理
void mixedInputProblem() {
std::string name;
int age;
std::string description;
// 問題のある実装
std::cout << "名前を入力: ";
std::getline(std::cin, name);
std::cout << "年齢を入力: ";
std::cin >> age;
std::cout << "説明を入力: ";
std::getline(std::cin, description); // バッファ問題発生
}
解決策2: 統一された入力処理
template<typename T>
T getInput(const std::string& prompt) {
T value;
std::string line;
bool valid = false;
while (!valid) {
std::cout << prompt;
if (std::getline(std::cin, line)) {
std::stringstream ss(line);
if (ss >> value && ss.eof()) {
valid = true;
} else {
std::cout << "無効な入力です。再度試してください。" << std::endl;
}
}
}
return value;
}
文字エンコーディングに関する注意点
問題: マルチバイト文字の取り扱い
void encodingProblem() {
std::string text;
std::getline(std::cin, text); // 日本語入力時に問題発生の可能性
}
解決策: ワイド文字列の使用
#include <codecvt>
#include <locale>
void handleMultibyteStrings() {
// ロケールの設定
std::locale::global(std::locale(""));
std::wcout.imbue(std::locale());
std::wcin.imbue(std::locale());
std::wstring wtext;
std::getline(std::wcin, wtext); // ワイド文字列として読み込み
// UTF-8への変換が必要な場合
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::string utf8_text = converter.to_bytes(wtext);
}
メモリリークを防ぐための適切な例外処理
問題: リソース管理の不備
void resourceLeakProblem() {
std::ifstream file("data.txt");
std::string line;
// 例外発生時にファイルが適切にクローズされない可能性
while (std::getline(file, line)) {
processLine(line); // 例外を投げる可能性がある関数
}
file.close();
}
解決策: RAIIパターンの活用
class FileReader {
private:
std::ifstream file;
public:
explicit FileReader(const std::string& filename) : file(filename) {
if (!file) {
throw std::runtime_error("ファイルを開けません: " + filename);
}
}
// デストラクタでファイルは自動的にクローズされる
~FileReader() = default;
bool getNextLine(std::string& line) {
return static_cast<bool>(std::getline(file, line));
}
};
void safeResourceHandling() {
try {
FileReader reader("data.txt");
std::string line;
while (reader.getNextLine(line)) {
try {
processLine(line);
} catch (const std::exception& e) {
std::cerr << "行の処理中にエラー: " << e.what() << std::endl;
// 処理は継続
}
}
} catch (const std::exception& e) {
std::cerr << "ファイル処理エラー: " << e.what() << std::endl;
}
}
エラー防止のためのベストプラクティス
- 入力ストリームの状態確認
if (!std::cin) {
std::cin.clear(); // エラーフラグをクリア
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
- バッファサイズの制限
std::string line;
line.reserve(1024); // 予想される最大長を指定
if (!std::getline(std::cin, line)) {
// エラー処理
}
- 例外安全な設計
class SafeLineReader {
public:
static std::optional<std::string> readLine(std::istream& is) {
std::string line;
return std::getline(is, line) ? std::optional<std::string>(line)
: std::nullopt;
}
};
これらの問題に適切に対処することで、より堅牢なプログラムを作成することができます。次のセクションでは、パフォーマンスの最適化について詳しく説明します。
パフォーマンス最適化テクニック
バッファサイズの最適な設定方法
std::getlineのパフォーマンスを最適化する上で、バッファサイズの適切な管理は重要な要素です。
メモリ事前確保による最適化
#include <string>
#include <chrono>
#include <iostream>
class BufferOptimizer {
private:
static constexpr size_t TYPICAL_LINE_LENGTH = 1024; // 想定される平均行長
public:
static void optimizeString(std::string& str) {
str.reserve(TYPICAL_LINE_LENGTH);
}
// パフォーマンス計測用ベンチマーク関数
static void benchmark() {
const int ITERATIONS = 100000;
// 最適化なし
auto start = std::chrono::high_resolution_clock::now();
std::string unoptimized;
for (int i = 0; i < ITERATIONS; ++i) {
std::getline(std::cin, unoptimized);
}
auto end = std::chrono::high_resolution_clock::now();
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
// 最適化あり
start = std::chrono::high_resolution_clock::now();
std::string optimized;
optimized.reserve(TYPICAL_LINE_LENGTH);
for (int i = 0; i < ITERATIONS; ++i) {
std::getline(std::cin, optimized);
}
end = std::chrono::high_resolution_clock::now();
auto duration2 = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "最適化なし: " << duration1.count() << "μs\n";
std::cout << "最適化あり: " << duration2.count() << "μs\n";
}
};
メモリアクセスを優先的に実現するパターン
stringストリームの最適化
#include <sstream>
#include <vector>
class OptimizedStringStream {
private:
std::stringstream ss;
public:
explicit OptimizedStringStream(size_t buffer_size = 4096) {
// 内部バッファサイズの設定
ss.rdbuf()->pubsetbuf(nullptr, buffer_size);
}
std::vector<std::string> splitLines(const std::string& input) {
ss.str(input);
ss.clear();
std::vector<std::string> lines;
std::string line;
while (std::getline(ss, line)) {
lines.emplace_back(std::move(line));
}
return lines;
}
};
大容量ファイル処理における効率化戦略
チャンク読み込みによる最適化
#include <fstream>
#include <vector>
#include <memory>
class ChunkedFileReader {
private:
static constexpr size_t CHUNK_SIZE = 8192; // 8KB chunks
std::ifstream file;
std::vector<char> buffer;
public:
explicit ChunkedFileReader(const std::string& filename)
: file(filename, std::ios::binary)
, buffer(CHUNK_SIZE) {
if (!file) {
throw std::runtime_error("ファイルを開けません: " + filename);
}
}
template<typename Callback>
void processLines(Callback callback) {
std::string partial_line;
while (file) {
// チャンク単位で読み込み
file.read(buffer.data(), buffer.size());
std::streamsize bytes_read = file.gcount();
if (bytes_read == 0) break;
// チャンク内の行を処理
for (std::streamsize i = 0; i < bytes_read; ++i) {
if (buffer[i] == '\n') {
callback(partial_line);
partial_line.clear();
} else {
partial_line += buffer[i];
}
}
}
// 最後の行の処理
if (!partial_line.empty()) {
callback(partial_line);
}
}
};
メモリマッピングを活用した最適化
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
class MemoryMappedFileReader {
private:
int fd;
char* mapped_data;
size_t file_size;
public:
explicit MemoryMappedFileReader(const std::string& filename) {
fd = open(filename.c_str(), O_RDONLY);
if (fd == -1) {
throw std::runtime_error("ファイルを開けません");
}
file_size = lseek(fd, 0, SEEK_END);
mapped_data = static_cast<char*>(
mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0)
);
if (mapped_data == MAP_FAILED) {
close(fd);
throw std::runtime_error("メモリマッピングに失敗しました");
}
}
~MemoryMappedFileReader() {
munmap(mapped_data, file_size);
close(fd);
}
template<typename Callback>
void processLines(Callback callback) {
const char* current = mapped_data;
const char* end = mapped_data + file_size;
std::string line;
while (current < end) {
if (*current == '\n') {
callback(line);
line.clear();
} else {
line += *current;
}
++current;
}
if (!line.empty()) {
callback(line);
}
}
};
パフォーマンス最適化のベストプラクティス
- stringのメモリ管理
- 可能な限り事前にreserveを使用
- 不必要なコピーを避けるためにmoveを活用
- 一時文字列の生成を最小限に抑える
- ストリーム最適化
- バッファサイズを適切に設定
- syncを必要に応じて無効化
- tiebufを効果的に使用
- 大規模データ処理
- チャンク読み込みの活用
- メモリマッピングの使用
- 並列処理の検討
- メモリ効率
// 効率的な文字列処理
void processLargeFile(const std::string& filename) {
std::ifstream file(filename);
std::string line;
line.reserve(4096); // 一般的な行サイズ
while (std::getline(file, line)) {
// 行の処理
line.clear(); // メモリを解放せずに再利用
}
}
これらの最適化テクニックを適切に組み合わせることで、std::getlineを使用したプログラムのパフォーマンスを大幅に向上させることができます。次のセクションでは、セキュリティ対策について説明します。
セキュリティ対策と堅牢な実装
バッファオーバーフロー攻撃からの保護方法
std::getlineは基本的にバッファオーバーフローから保護されていますが、追加の安全対策を実装することで、より堅牢なコードを実現できます。
安全な入力処理の実装
#include <string>
#include <limits>
#include <stdexcept>
class SecureInputHandler {
private:
static constexpr size_t MAX_INPUT_LENGTH = 1024 * 1024; // 1MB
public:
static std::string getSecureInput(std::istream& input) {
std::string line;
// 入力の長さを制限
if (!std::getline(input, line)) {
throw std::runtime_error("入力の読み取りに失敗しました");
}
if (line.length() > MAX_INPUT_LENGTH) {
throw std::length_error("入力が最大許容長を超えています");
}
return line;
}
static std::string getSanitizedInput(std::istream& input) {
std::string line = getSecureInput(input);
sanitizeInput(line);
return line;
}
private:
static void sanitizeInput(std::string& input) {
// 制御文字の除去
input.erase(
std::remove_if(input.begin(), input.end(),
[](char c) { return std::iscntrl(c) && c != '\n'; }),
input.end()
);
}
};
入力データのバリデーション実装例
堅牢な入力検証システム
#include <regex>
#include <optional>
class InputValidator {
public:
struct ValidationRules {
size_t minLength = 0;
size_t maxLength = std::numeric_limits<size_t>::max();
std::optional<std::regex> pattern;
bool allowSpecialChars = true;
};
static bool validate(const std::string& input, const ValidationRules& rules) {
// 長さの検証
if (input.length() < rules.minLength || input.length() > rules.maxLength) {
return false;
}
// パターンの検証
if (rules.pattern && !std::regex_match(input, rules.pattern.value())) {
return false;
}
// 特殊文字の検証
if (!rules.allowSpecialChars) {
if (std::any_of(input.begin(), input.end(),
[](char c) { return !std::isalnum(c) && !std::isspace(c); })) {
return false;
}
}
return true;
}
};
// 使用例
class SecureFileReader {
private:
InputValidator::ValidationRules rules;
public:
SecureFileReader() {
rules.maxLength = 4096; // 最大4KB
rules.pattern = std::regex(R"([a-zA-Z0-9\s\-_\.]+)"); // 安全な文字のみ
rules.allowSpecialChars = false;
}
std::vector<std::string> readLines(const std::string& filename) {
std::vector<std::string> validLines;
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
if (InputValidator::validate(line, rules)) {
validLines.push_back(line);
} else {
// 無効な入力の処理(ログ記録、エラー通知など)
handleInvalidInput(line);
}
}
return validLines;
}
private:
void handleInvalidInput(const std::string& input) {
// エラーログの記録
std::cerr << "Invalid input detected: " << input << std::endl;
}
};
セキュアな実装のためのベストプラクティス
- 入力の制限と検証
template<typename T>
class InputLimiter {
public:
static std::optional<T> getValidInput(std::istream& input) {
std::string line;
try {
if (!std::getline(input, line)) {
return std::nullopt;
}
// 入力の検証
if (!validateInput(line)) {
return std::nullopt;
}
// 型変換(必要な場合)
return convertInput(line);
} catch (const std::exception& e) {
// エラーログの記録
return std::nullopt;
}
}
private:
static bool validateInput(const std::string& input) {
// アプリケーション固有の検証ルール
return true;
}
static std::optional<T> convertInput(const std::string& input) {
// 型変換の実装
return T();
}
};
- エラー処理とログ記録
class SecureLogger {
private:
static std::mutex logMutex;
public:
static void logSecurityEvent(const std::string& event,
const std::string& severity = "INFO") {
std::lock_guard<std::mutex> lock(logMutex);
auto now = std::chrono::system_clock::now();
auto timestamp = std::chrono::system_clock::to_time_t(now);
std::ofstream logFile("security.log", std::ios::app);
logFile << std::put_time(std::localtime(×tamp), "%Y-%m-%d %H:%M:%S")
<< " [" << severity << "] " << event << std::endl;
}
};
- リソース管理の安全性確保
class SecureResource {
private:
std::unique_ptr<std::ifstream> file;
public:
explicit SecureResource(const std::string& filename) {
file = std::make_unique<std::ifstream>(filename);
if (!file->is_open()) {
throw std::runtime_error("リソースを開けません");
}
}
// RAII による自動クリーンアップ
~SecureResource() = default;
// ムーブのみを許可し、コピーを禁止
SecureResource(SecureResource&&) = default;
SecureResource& operator=(SecureResource&&) = default;
SecureResource(const SecureResource&) = delete;
SecureResource& operator=(const SecureResource&) = delete;
};
これらのセキュリティ対策と堅牢な実装パターンを適用することで、より安全なプログラムを作成することができます。次のセクションでは、これらの知識を活用した具体的な応用例を示します。
応用実装例とサンプルコード
設定ファイルパーサーの実装例
INIファイル形式の設定ファイルを解析する実装例です。セクションとキー・バリューペアを処理します。
#include <map>
#include <string>
#include <fstream>
#include <regex>
class ConfigParser {
public:
using Section = std::map<std::string, std::string>;
using Config = std::map<std::string, Section>;
private:
Config config;
static const std::regex sectionRegex;
static const std::regex keyValueRegex;
public:
explicit ConfigParser(const std::string& filename) {
std::ifstream file(filename);
if (!file) {
throw std::runtime_error("設定ファイルを開けません: " + filename);
}
std::string currentSection;
std::string line;
while (std::getline(file, line)) {
// 空行とコメントをスキップ
if (line.empty() || line[0] == ';' || line[0] == '#') {
continue;
}
// セクション名の処理
std::smatch match;
if (std::regex_match(line, match, sectionRegex)) {
currentSection = match[1].str();
continue;
}
// キー・バリューペアの処理
if (std::regex_match(line, match, keyValueRegex)) {
std::string key = match[1].str();
std::string value = match[2].str();
config[currentSection][key] = value;
}
}
}
std::string getValue(const std::string& section,
const std::string& key,
const std::string& defaultValue = "") const {
auto sectionIt = config.find(section);
if (sectionIt != config.end()) {
auto keyIt = sectionIt->second.find(key);
if (keyIt != sectionIt->second.end()) {
return keyIt->second;
}
}
return defaultValue;
}
};
const std::regex ConfigParser::sectionRegex(R"(\[([^\]]+)\])");
const std::regex ConfigParser::keyValueRegex(R"((\w+)\s*=\s*(.*))");
// 使用例
void configExample() {
try {
ConfigParser parser("config.ini");
std::string dbHost = parser.getValue("Database", "host", "localhost");
int dbPort = std::stoi(parser.getValue("Database", "port", "3306"));
// 設定値の使用
std::cout << "Database Host: " << dbHost << std::endl;
std::cout << "Database Port: " << dbPort << std::endl;
} catch (const std::exception& e) {
std::cerr << "設定ファイルの読み込みエラー: " << e.what() << std::endl;
}
}
ログファイル解析ツールの作成手順
ログファイルを解析し、特定のパターンを検出・集計するツールの実装例です。
#include <chrono>
#include <iomanip>
#include <vector>
#include <algorithm>
class LogAnalyzer {
public:
struct LogEntry {
std::chrono::system_clock::time_point timestamp;
std::string level;
std::string message;
};
private:
std::vector<LogEntry> entries;
static const std::regex timestampRegex;
static const std::regex logPatternRegex;
public:
void parseLogFile(const std::string& filename) {
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
auto entry = parseLine(line);
if (entry) {
entries.push_back(*entry);
}
}
}
std::vector<LogEntry> filterByLevel(const std::string& level) {
std::vector<LogEntry> filtered;
std::copy_if(entries.begin(), entries.end(),
std::back_inserter(filtered),
[&level](const LogEntry& entry) {
return entry.level == level;
});
return filtered;
}
std::vector<LogEntry> filterByTimeRange(
const std::chrono::system_clock::time_point& start,
const std::chrono::system_clock::time_point& end) {
std::vector<LogEntry> filtered;
std::copy_if(entries.begin(), entries.end(),
std::back_inserter(filtered),
[&](const LogEntry& entry) {
return entry.timestamp >= start &&
entry.timestamp <= end;
});
return filtered;
}
private:
std::optional<LogEntry> parseLine(const std::string& line) {
std::smatch match;
if (std::regex_match(line, match, logPatternRegex)) {
LogEntry entry;
entry.timestamp = parseTimestamp(match[1].str());
entry.level = match[2].str();
entry.message = match[3].str();
return entry;
}
return std::nullopt;
}
static std::chrono::system_clock::time_point parseTimestamp(
const std::string& timestamp) {
std::tm tm = {};
std::stringstream ss(timestamp);
ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
return std::chrono::system_clock::from_time_t(std::mktime(&tm));
}
};
対話型コンソールアプリケーションの実装テクニック
コマンドラインインターフェース(CLI)アプリケーションの実装例です。
#include <functional>
#include <memory>
class ConsoleApp {
public:
using CommandFunc = std::function<void(const std::vector<std::string>&)>;
private:
std::map<std::string, CommandFunc> commands;
std::string prompt = "> ";
bool running = true;
// コマンド履歴
std::vector<std::string> history;
static const size_t MAX_HISTORY = 100;
public:
ConsoleApp() {
registerBaseCommands();
}
void registerCommand(const std::string& name, CommandFunc func) {
commands[name] = std::move(func);
}
void run() {
std::cout << "対話型コンソールを起動しました。'help'でコマンド一覧を表示します。\n";
std::string line;
while (running && std::cout << prompt && std::getline(std::cin, line)) {
if (!line.empty()) {
executeCommand(line);
addToHistory(line);
}
}
}
private:
void registerBaseCommands() {
// 基本コマンドの登録
commands["help"] = [this](const auto&) {
std::cout << "利用可能なコマンド:\n";
for (const auto& [name, _] : commands) {
std::cout << " " << name << "\n";
}
};
commands["exit"] = [this](const auto&) {
running = false;
};
commands["history"] = [this](const auto&) {
for (size_t i = 0; i < history.size(); ++i) {
std::cout << i + 1 << ": " << history[i] << "\n";
}
};
}
void executeCommand(const std::string& line) {
auto args = parseCommandLine(line);
if (args.empty()) return;
auto command = args[0];
args.erase(args.begin());
auto it = commands.find(command);
if (it != commands.end()) {
try {
it->second(args);
} catch (const std::exception& e) {
std::cerr << "エラー: " << e.what() << std::endl;
}
} else {
std::cout << "未知のコマンド: " << command << "\n";
}
}
static std::vector<std::string> parseCommandLine(const std::string& line) {
std::vector<std::string> args;
std::stringstream ss(line);
std::string arg;
while (ss >> std::quoted(arg)) {
args.push_back(arg);
}
return args;
}
void addToHistory(const std::string& line) {
if (history.size() >= MAX_HISTORY) {
history.erase(history.begin());
}
history.push_back(line);
}
};
// 使用例
int main() {
ConsoleApp app;
// カスタムコマンドの登録
app.registerCommand("echo", [](const std::vector<std::string>& args) {
for (const auto& arg : args) {
std::cout << arg << " ";
}
std::cout << "\n";
});
app.registerCommand("calc", [](const std::vector<std::string>& args) {
if (args.size() != 2) {
throw std::runtime_error("使用法: calc <num1> <num2>");
}
int a = std::stoi(args[0]);
int b = std::stoi(args[1]);
std::cout << "合計: " << (a + b) << "\n";
});
app.run();
return 0;
}
これらの実装例は、実際の開発現場で直面する可能性のある要件に基づいています。各実装には以下の特徴があります:
- 設定ファイルパーサー
- セクション管理による階層的な設定
- 正規表現を使用した堅牢な解析
- デフォルト値のサポート
- ログ解析ツール
- 柔軟な時間範囲フィルタリング
- ログレベルによる絞り込み
- 効率的なメモリ使用
- 対話型コンソール
- コマンド履歴の管理
- プラグイン的なコマンド登録システム
- 堅牢なエラー処理
これらの実装例を基に、プロジェクトの要件に合わせてカスタマイズすることで、効率的な開発が可能になります。