C++サンプルコードの基本と活用方法
C++での開発を効率的に進めるためには、サンプルコードを正しく理解し、自身のプロジェクトに適用できる能力が不可欠です。このセクションでは、プロフェッショナルな視点からサンプルコードの活用方法と、実践的な開発環境の構築方法について解説します。
プロフェッショナルが教えるサンプルコードの読み方
サンプルコードを効果的に活用するためには、以下の点に注目して読み解くことが重要です:
- コードの全体構造の把握
// 典型的なC++プログラムの構造
#include <iostream>
#include <string>
// 名前空間の利用
namespace example {
class MyClass {
private:
std::string data;
public:
MyClass(const std::string& input) : data(input) {}
void display() const {
std::cout << "Data: " << data << std::endl;
}
};
}
int main() {
example::MyClass obj("Hello World");
obj.display();
return 0;
}
このコードから読み取るべきポイント:
- ヘッダーファイルのインクルード方法
- 名前空間の適切な使用
- クラス定義の基本構造
- メンバ初期化リストの使用
- const修飾子の適切な配置
- メモリ管理とリソースの取り扱い
// リソース管理の基本パターン
#include <memory>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
};
void resourceExample() {
// スマートポインタの使用
std::unique_ptr<Resource> res = std::make_unique<Resource>();
// リソースは関数終了時に自動的に解放される
}
開発環境の整備とコードの実行方法
効率的なC++開発には、適切な開発環境の構築が不可欠です。以下に、推奨される環境セットアップ手順を示します:
- コンパイラの選択と設定
# GCCの場合(Linux/macOS) g++ -std=c++17 -Wall -Wextra sample.cpp -o sample # Clangの場合 clang++ -std=c++17 -Wall -Wextra sample.cpp -o sample # MSVCの場合(Windows) cl /std:c++17 /W4 sample.cpp
- 効果的なデバッグ環境の構築
// デバッグ用のマクロ例
#ifdef DEBUG
#define DEBUG_PRINT(x) std::cout << x << std::endl
#else
#define DEBUG_PRINT(x)
#endif
void debugExample() {
DEBUG_PRINT("Debug message");
// リリースビルドでは出力されない
}
重要な開発ツール:
- Visual Studio Code + C/C++ 拡張機能
- CMake ビルドシステム
- GDB/LLDB デバッガ
- Valgrind メモリチェッカー
- ビルド構成の最適化
# CMakeLists.txt の基本例 cmake_minimum_required(VERSION 3.10) project(MyProject) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(myapp main.cpp)
サンプルコードを実行する際の主なチェックポイント:
- コンパイラの警告レベルを最大に設定
- 使用するC++標準バージョンの明示的な指定
- 依存ライブラリの適切な管理
- デバッグビルドとリリースビルドの使い分け
これらの基本を押さえることで、以降のセクションで紹介する様々なサンプルコードを効果的に活用し、実践的なC++プログラミングのスキルを向上させることができます。
C++基礎文法のサンプルコード集
実践的なC++プログラミングの基礎となる文法要素について、具体的なコード例と共に解説していきます。
変数とデータ型の実践的な使用例
- 基本データ型の適切な使用
#include <iostream>
#include <limits>
#include <cstdint>
void dataTypeExample() {
// 固定幅整数型の使用
int32_t exact_32 = 2147483647; // 正確に32ビット
uint64_t large_num = 18446744073709551615ULL; // 大きな正の整数
// 浮動小数点数の精度
float f = 3.14159f; // 単精度
double d = 3.14159265359; // 倍精度
// 数値の限界値の取得
auto max_int = std::numeric_limits<int>::max();
auto min_int = std::numeric_limits<int>::min();
std::cout << "int32_t max: " << exact_32 << "\n"
<< "uint64_t value: " << large_num << "\n"
<< "float precision: " << f << "\n"
<< "double precision: " << d << "\n"
<< "int limits: " << min_int << " to " << max_int << std::endl;
}
- 文字列処理の効率的な実装
#include <string>
#include <string_view>
void stringProcessingExample() {
// 文字列の効率的な初期化
std::string str1 = "Hello";
std::string str2{"World"}; // 統一初期化構文
// string_viewによる効率的な文字列参照
std::string_view sv = "This is a long string";
// 文字列の結合と操作
str1 += " " + str2; // 文字列連結
// 部分文字列の取得
auto substr = str1.substr(0, 5); // "Hello"
// 文字列の検索
size_t pos = str1.find("World");
if (pos != std::string::npos) {
std::cout << "Found 'World' at position: " << pos << std::endl;
}
}
制御構文で作る堅牢なプログラム
- 条件分岐の効果的な使用
#include <variant>
#include <stdexcept>
// モダンな条件分岐の実装例
enum class ErrorCode { None, InvalidInput, NetworkError };
std::variant<int, ErrorCode> processValue(int input) {
if (input < 0) {
return ErrorCode::InvalidInput;
}
if (input > 100) {
return ErrorCode::NetworkError;
}
return input * 2;
}
void controlFlowExample() {
auto result = processValue(50);
if (auto value = std::get_if<int>(&result)) {
std::cout << "Success: " << *value << std::endl;
} else {
auto error = std::get<ErrorCode>(result);
switch (error) {
case ErrorCode::InvalidInput:
std::cout << "Invalid input" << std::endl;
break;
case ErrorCode::NetworkError:
std::cout << "Network error" << std::endl;
break;
default:
std::cout << "Unknown error" << std::endl;
}
}
}
- ループ処理の最適化
#include <vector>
#include <algorithm>
void loopOptimizationExample() {
std::vector<int> numbers{1, 2, 3, 4, 5};
// 範囲ベースのfor文
for (const auto& num : numbers) {
std::cout << num << " ";
}
// アルゴリズムを使用した効率的な処理
auto sum = std::accumulate(numbers.begin(), numbers.end(), 0);
// 条件付きループ
auto it = std::find_if(numbers.begin(), numbers.end(),
[](int n) { return n > 3; });
}
関数とクラスの実装テクニック
- モダンな関数設計
#include <functional>
// 関数テンプレートと完全転送
template<typename T>
auto processData(T&& value) {
return std::forward<T>(value);
}
// 関数オブジェクトの活用
class Calculator {
public:
template<typename T>
T add(T a, T b) const { return a + b; }
auto multiply(auto a, auto b) const { // C++20
return a * b;
}
};
// 関数オーバーロードの適切な使用
void display(int value) {
std::cout << "Integer: " << value << std::endl;
}
void display(double value) {
std::cout << "Double: " << std::fixed << value << std::endl;
}
- クラス設計のベストプラクティス
class Resource {
private:
std::string name;
std::unique_ptr<int[]> data;
size_t size;
public:
// 現代的なコンストラクタ設計
explicit Resource(std::string name, size_t size)
: name(std::move(name))
, data(std::make_unique<int[]>(size))
, size(size) {}
// ムーブセマンティクスの実装
Resource(Resource&& other) noexcept
: name(std::move(other.name))
, data(std::move(other.data))
, size(other.size) {
other.size = 0;
}
// Rule of Five の実装
Resource& operator=(Resource&& other) noexcept {
if (this != &other) {
name = std::move(other.name);
data = std::move(other.data);
size = other.size;
other.size = 0;
}
return *this;
}
// デフォルトの特殊関数の制御
Resource(const Resource&) = delete;
Resource& operator=(const Resource&) = delete;
// 仮想デストラクタ
virtual ~Resource() = default;
};
これらのサンプルコードは、実際の開発現場で頻繁に使用される基本的なパターンを示しています。各コードは、最新のC++標準に準拠し、パフォーマンスと保守性を考慮して設計されています。
STLライブラリを使いこなすサンプルコード
STL(Standard Template Library)は、C++開発において必須の機能を提供するライブラリです。ここでは、実践的な使用例を通じてSTLの効果的な活用方法を解説します。
vectorとlistで実現するデータ構造
- vectorの効率的な使用
#include <vector>
#include <algorithm>
class DataManager {
private:
std::vector<int> data;
public:
// vectorの効率的な初期化
void initialize(size_t size) {
// 必要なサイズを予約してコピーを最小化
data.reserve(size);
// 効率的なデータ追加
for (size_t i = 0; i < size; ++i) {
data.emplace_back(i); // コピーを避けて直接構築
}
}
// 効率的な検索と更新
void updateElements() {
// 範囲for文による効率的なイテレーション
for (auto& elem : data) {
elem *= 2;
}
// アルゴリズムによる並列処理
std::sort(data.begin(), data.end());
// 二分探索による高速な検索
if (std::binary_search(data.begin(), data.end(), 10)) {
std::cout << "Found element 10" << std::endl;
}
}
// データの効率的な削除
void removeElements() {
// erase-removeイディオムによる効率的な削除
data.erase(
std::remove_if(data.begin(), data.end(),
[](int n) { return n % 2 == 0; }),
data.end()
);
}
};
- listの特性を活かした実装
#include <list>
#include <algorithm>
class TaskScheduler {
private:
std::list<std::string> tasks;
public:
// 要素の挿入と削除が頻繁な場合はlistが有利
void addTask(const std::string& task) {
// 先頭または末尾への追加はO(1)
tasks.push_back(task);
}
void removeTask(const std::string& task) {
// 要素の削除もO(1)(イテレータが無効化されない)
tasks.remove(task);
}
// リストの分割と結合
std::list<std::string> splitTasks() {
std::list<std::string> highPriorityTasks;
auto it = std::partition(tasks.begin(), tasks.end(),
[](const std::string& task) {
return task.starts_with("HIGH:");
});
// 分割されたタスクを新しいリストに移動
highPriorityTasks.splice(highPriorityTasks.begin(),
tasks, tasks.begin(), it);
return highPriorityTasks;
}
};
mapとsetによる効率的なデータ管理
- 連想コンテナの活用
#include <map>
#include <set>
#include <string>
class CacheManager {
private:
std::map<std::string, std::string> cache;
std::set<std::string> uniqueKeys;
public:
// mapによる効率的なキャッシュ管理
void addToCache(const std::string& key, const std::string& value) {
// 挿入の成否を確認
auto [it, inserted] = cache.insert_or_assign(key, value);
if (inserted) {
std::cout << "New key added: " << key << std::endl;
} else {
std::cout << "Key updated: " << key << std::endl;
}
// ユニークなキーの追跡
uniqueKeys.insert(key);
}
// 範囲検索の実装
std::vector<std::string> getKeysInRange(
const std::string& start, const std::string& end) {
std::vector<std::string> result;
auto startIt = uniqueKeys.lower_bound(start);
auto endIt = uniqueKeys.upper_bound(end);
std::copy(startIt, endIt, std::back_inserter(result));
return result;
}
};
- 複雑なデータ構造の実装
#include <unordered_map>
#include <set>
class UserManager {
private:
// ユーザーIDとスコアの管理
std::unordered_map<int, double> userScores;
// スコアによるランキング
std::multiset<std::pair<double, int>> scoreRanking;
public:
// ユーザースコアの更新と順位の維持
void updateScore(int userId, double newScore) {
auto oldScoreIt = userScores.find(userId);
if (oldScoreIt != userScores.end()) {
// 古いスコアをランキングから削除
scoreRanking.erase(
scoreRanking.find({oldScoreIt->second, userId})
);
}
// 新しいスコアを登録
userScores[userId] = newScore;
scoreRanking.insert({newScore, userId});
}
// トップNユーザーの取得
std::vector<int> getTopUsers(size_t n) {
std::vector<int> topUsers;
auto it = scoreRanking.rbegin();
while (topUsers.size() < n && it != scoreRanking.rend()) {
topUsers.push_back(it->second);
++it;
}
return topUsers;
}
};
アルゴリズム関数で実現する高速処理
- アルゴリズムの組み合わせによる効率的な処理
#include <algorithm>
#include <numeric>
#include <execution>
class DataProcessor {
private:
std::vector<double> data;
public:
// 並列アルゴリズムの活用
double processData() {
// 並列ソート
std::sort(std::execution::par,
data.begin(), data.end());
// 外れ値の除去
auto [minIt, maxIt] = std::minmax_element(
data.begin(), data.end());
data.erase(std::remove_if(data.begin(), data.end(),
[min = *minIt, max = *maxIt](double x) {
return x < min * 0.1 || x > max * 0.9;
}), data.end());
// 平均値の計算
return std::reduce(std::execution::par,
data.begin(), data.end())
/ data.size();
}
// カスタムアルゴリズムの実装
template<typename Pred>
void transformIf(Pred predicate) {
std::vector<double> temp;
temp.reserve(data.size());
// 条件を満たす要素のみを変換
std::copy_if(data.begin(), data.end(),
std::back_inserter(temp),
predicate);
// 変換結果で更新
data = std::move(temp);
}
};
これらのサンプルコードは、STLを効果的に活用するための実践的なパターンを示しています。特に、パフォーマンスとメモリ効率を考慮した実装方法に注目してください。
メモリ管理のベストプラクティス
C++におけるメモリ管理は、プログラムのパフォーマンスと安全性に直接影響を与える重要な要素です。ここでは、現代的なC++におけるメモリ管理のベストプラクティスを解説します。
スマートポインタを活用した安全なコード
- unique_ptrの適切な使用
#include <memory>
#include <vector>
class ResourceManager {
private:
// 排他的所有権を持つリソースの管理
std::unique_ptr<int[]> buffer;
size_t size;
public:
explicit ResourceManager(size_t bufferSize)
: buffer(std::make_unique<int[]>(bufferSize))
, size(bufferSize) {}
// unique_ptrを返す関数の実装
std::unique_ptr<int[]> createBuffer(size_t newSize) {
return std::make_unique<int[]>(newSize);
}
// unique_ptrのムーブセマンティクス
void updateBuffer(std::unique_ptr<int[]> newBuffer, size_t newSize) {
buffer = std::move(newBuffer);
size = newSize;
}
// コンテナでのunique_ptrの使用
std::vector<std::unique_ptr<int>> createItems() {
std::vector<std::unique_ptr<int>> items;
items.push_back(std::make_unique<int>(42));
return items; // ムーブセマンティクスにより安全に返却
}
};
- shared_ptrと参照カウントの管理
#include <memory>
#include <unordered_map>
class SharedResource {
public:
virtual ~SharedResource() = default;
virtual void use() = 0;
};
class Cache {
private:
// 共有リソースのキャッシュ
std::unordered_map<std::string, std::shared_ptr<SharedResource>> resources;
// 循環参照を防ぐためのweak_ptr
std::unordered_map<std::string, std::weak_ptr<SharedResource>> weakCache;
public:
// リソースの取得と共有
std::shared_ptr<SharedResource> getResource(const std::string& key) {
// まずweakキャッシュをチェック
if (auto weakIt = weakCache.find(key); weakIt != weakCache.end()) {
if (auto resource = weakIt->second.lock()) {
return resource;
}
weakCache.erase(weakIt);
}
// 通常のキャッシュをチェック
if (auto it = resources.find(key); it != resources.end()) {
return it->second;
}
// 新しいリソースを作成
auto newResource = std::make_shared<ConcreteResource>();
resources[key] = newResource;
weakCache[key] = newResource; // 弱参照も保持
return newResource;
}
};
メモリリークを防ぐ実装パターン
- RAIIパターンの実装
#include <memory>
#include <mutex>
class RAIIExample {
private:
class ScopedLock {
std::mutex& mutex;
public:
explicit ScopedLock(std::mutex& m) : mutex(m) {
mutex.lock();
}
~ScopedLock() {
mutex.unlock();
}
// コピー禁止
ScopedLock(const ScopedLock&) = delete;
ScopedLock& operator=(const ScopedLock&) = delete;
};
std::mutex resourceMutex;
std::unique_ptr<int> resource;
public:
void useResource() {
// スコープベースのリソース管理
ScopedLock lock(resourceMutex);
// 例外が発生しても、デストラクタによってミューテックスは解放される
resource = std::make_unique<int>(42);
// リソースを使用...
} // スコープを抜けると自動的にアンロック
};
- カスタムデリータの実装
#include <memory>
#include <functional>
class FileHandle {
private:
using FileDeleter = std::function<void(FILE*)>;
std::unique_ptr<FILE, FileDeleter> file;
public:
FileHandle(const char* filename, const char* mode)
: file(fopen(filename, mode), [](FILE* f) {
if (f) fclose(f);
}) {
if (!file) {
throw std::runtime_error("Failed to open file");
}
}
// ファイル操作メソッド
void writeData(const void* data, size_t size) {
if (fwrite(data, 1, size, file.get()) != size) {
throw std::runtime_error("Write failed");
}
}
};
- メモリプールの実装
#include <memory>
#include <vector>
template<typename T, size_t BlockSize = 4096>
class MemoryPool {
private:
struct Block {
std::array<T, BlockSize> data;
size_t used = 0;
};
std::vector<std::unique_ptr<Block>> blocks;
size_t currentBlock = 0;
public:
template<typename... Args>
T* allocate(Args&&... args) {
if (currentBlock >= blocks.size() ||
blocks[currentBlock]->used >= BlockSize) {
// 新しいブロックを追加
blocks.push_back(std::make_unique<Block>());
currentBlock = blocks.size() - 1;
}
// 現在のブロックから割り当て
auto& block = blocks[currentBlock];
T* ptr = &block->data[block->used++];
new(ptr) T(std::forward<Args>(args)...);
return ptr;
}
void deallocate(T* ptr) {
if (ptr) {
ptr->~T(); // デストラクタを明示的に呼び出し
}
// メモリは実際には解放せず、再利用する
}
void clear() {
for (auto& block : blocks) {
for (size_t i = 0; i < block->used; ++i) {
block->data[i].~T();
}
block->used = 0;
}
currentBlock = 0;
}
};
これらのサンプルコードは、現代的なC++におけるメモリ管理のベストプラクティスを示しています。特に、リソースの自動管理、メモリリークの防止、そしてパフォーマンスの最適化に焦点を当てています。
マルチスレッドプログラミングの実践
マルチスレッドプログラミングは現代のC++開発において重要な要素です。ここでは、実践的なマルチスレッドプログラミングの手法と、安全な並行処理の実装方法を解説します。
スレッド制御の基本パターン
- 基本的なスレッド管理
#include <thread>
#include <vector>
#include <functional>
class ThreadPool {
private:
std::vector<std::thread> workers;
std::vector<std::function<void()>> tasks;
std::mutex taskMutex;
std::condition_variable condition;
bool stop;
public:
explicit ThreadPool(size_t numThreads) : stop(false) {
for (size_t i = 0; i < numThreads; ++i) {
workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(taskMutex);
condition.wait(lock, [this] {
return stop || !tasks.empty();
});
if (stop && tasks.empty()) {
return;
}
task = std::move(tasks.back());
tasks.pop_back();
}
task();
}
});
}
}
template<typename F>
void enqueue(F&& task) {
{
std::lock_guard<std::mutex> lock(taskMutex);
tasks.emplace_back(std::forward<F>(task));
}
condition.notify_one();
}
~ThreadPool() {
{
std::lock_guard<std::mutex> lock(taskMutex);
stop = true;
}
condition.notify_all();
for (auto& worker : workers) {
worker.join();
}
}
};
- スレッド間通信の実装
#include <queue>
#include <optional>
template<typename T>
class ThreadSafeQueue {
private:
mutable std::mutex mutex;
std::queue<T> queue;
std::condition_variable notEmpty;
public:
void push(T value) {
std::lock_guard<std::mutex> lock(mutex);
queue.push(std::move(value));
notEmpty.notify_one();
}
std::optional<T> tryPop() {
std::lock_guard<std::mutex> lock(mutex);
if (queue.empty()) {
return std::nullopt;
}
T value = std::move(queue.front());
queue.pop();
return value;
}
T waitAndPop() {
std::unique_lock<std::mutex> lock(mutex);
notEmpty.wait(lock, [this] { return !queue.empty(); });
T value = std::move(queue.front());
queue.pop();
return value;
}
};
排他制御で実現する安全な並行処理
- 複数のロック機構の組み合わせ
#include <shared_mutex>
#include <unordered_map>
template<typename Key, typename Value>
class ThreadSafeMap {
private:
std::unordered_map<Key, Value> data;
mutable std::shared_mutex mutex;
public:
// 読み取り専用操作(共有ロック)
std::optional<Value> find(const Key& key) const {
std::shared_lock<std::shared_mutex> lock(mutex);
if (auto it = data.find(key); it != data.end()) {
return it->second;
}
return std::nullopt;
}
// 書き込み操作(排他ロック)
void insert(const Key& key, Value value) {
std::unique_lock<std::shared_mutex> lock(mutex);
data[key] = std::move(value);
}
// 条件付き更新
template<typename F>
void update_if(const Key& key, F updateFunc) {
std::unique_lock<std::shared_mutex> lock(mutex);
if (auto it = data.find(key); it != data.end()) {
it->second = updateFunc(it->second);
}
}
};
- デッドロック防止パターン
class ResourceManager {
private:
std::mutex resourceA;
std::mutex resourceB;
// デッドロック防止のためのヘルパー関数
template<typename F>
void executeWithOrderedLocks(F&& func) {
std::lock(resourceA, resourceB);
std::lock_guard<std::mutex> lockA(resourceA, std::adopt_lock);
std::lock_guard<std::mutex> lockB(resourceB, std::adopt_lock);
func();
}
public:
void transferResources() {
executeWithOrderedLocks([this] {
// 安全なリソース転送処理
});
}
// スコープベースのロック管理
class ScopedLock {
ResourceManager& manager;
public:
explicit ScopedLock(ResourceManager& m) : manager(m) {
std::lock(manager.resourceA, manager.resourceB);
}
~ScopedLock() {
manager.resourceB.unlock();
manager.resourceA.unlock();
}
};
};
- 非同期処理のパターン
#include <future>
#include <chrono>
class AsyncProcessor {
public:
template<typename F>
auto submitTask(F&& task) {
return std::async(std::launch::async, std::forward<F>(task));
}
template<typename F>
auto submitWithTimeout(F&& task, std::chrono::milliseconds timeout) {
auto future = std::async(std::launch::async, std::forward<F>(task));
if (future.wait_for(timeout) == std::future_status::timeout) {
throw std::runtime_error("Task timed out");
}
return future.get();
}
// 複数の非同期タスクの並列実行
template<typename F>
void parallelFor(size_t start, size_t end, F&& func) {
const size_t numThreads = std::thread::hardware_concurrency();
const size_t batchSize = (end - start) / numThreads;
std::vector<std::future<void>> futures;
for (size_t i = 0; i < numThreads; ++i) {
size_t batchStart = start + i * batchSize;
size_t batchEnd = (i == numThreads - 1) ? end : batchStart + batchSize;
futures.push_back(std::async(std::launch::async, [=, &func] {
for (size_t j = batchStart; j < batchEnd; ++j) {
func(j);
}
}));
}
// 全てのタスクの完了を待機
for (auto& future : futures) {
future.get();
}
}
};
これらのサンプルコードは、マルチスレッドプログラミングにおける重要な実装パターンを示しています。特に、スレッドの安全性、効率的な同期処理、そしてデッドロック防止に焦点を当てています。
モダンC++で実現する効率的なコーディング
モダンC++は、より安全で効率的なコードを書くための多くの機能を提供しています。ここでは、これらの機能を活用した実践的なコーディング手法を解説します。
ラムダ式を活用した簡潔なコード
- ラムダ式の高度な使用法
#include <functional>
#include <memory>
#include <algorithm>
class EventSystem {
private:
// イベントハンドラの型定義
using EventHandler = std::function<void(const std::string&)>;
std::vector<EventHandler> handlers;
public:
// ジェネリックなイベントハンドラの登録
template<typename F>
void addEventListener(F&& handler) {
handlers.push_back(std::forward<F>(handler));
}
// 状態をキャプチャするラムダの使用例
void setupStatefulHandler(int& counter) {
addEventListener([&counter](const std::string& event) {
counter += event.length();
std::cout << "Current count: " << counter << std::endl;
});
}
// mutableラムダの使用例
auto createStatefulHandler() {
return [count = 0](const std::string& event) mutable {
++count;
std::cout << "Event #" << count << ": " << event << std::endl;
};
}
// ジェネリックラムダの活用
template<typename Container>
auto makeFilter() {
return [](const Container& container, auto predicate) {
Container result;
std::copy_if(container.begin(), container.end(),
std::back_inserter(result), predicate);
return result;
};
}
};
- STLアルゴリズムとラムダの組み合わせ
#include <algorithm>
#include <numeric>
class DataProcessor {
public:
template<typename Container>
void processData(Container& data) {
// データの変換
std::transform(data.begin(), data.end(), data.begin(),
[](auto& item) {
if constexpr (std::is_arithmetic_v<std::decay_t<decltype(item)>>) {
return item * 2;
} else {
return item;
}
});
// 条件付きフィルタリング
auto newEnd = std::remove_if(data.begin(), data.end(),
[](const auto& item) {
if constexpr (std::is_arithmetic_v<std::decay_t<decltype(item)>>) {
return item < 0;
} else {
return false;
}
});
data.erase(newEnd, data.end());
}
// 複合条件での並べ替え
template<typename T>
void sortWithMultipleCriteria(std::vector<T>& items) {
std::sort(items.begin(), items.end(),
[](const auto& a, const auto& b) {
// 優先順位に基づく比較
if (a.priority != b.priority) {
return a.priority > b.priority;
}
if (a.timestamp != b.timestamp) {
return a.timestamp < b.timestamp;
}
return a.id < b.id;
});
}
};
move semanticsによるパフォーマンス最適化
- 効率的なリソース管理
class ResourceHolder {
private:
std::unique_ptr<std::vector<int>> data;
public:
// ムーブコンストラクタ
ResourceHolder(ResourceHolder&& other) noexcept
: data(std::move(other.data)) {}
// ムーブ代入演算子
ResourceHolder& operator=(ResourceHolder&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
}
return *this;
}
// 完全転送を使用したリソースの追加
template<typename T>
void addResource(T&& resource) {
if (!data) {
data = std::make_unique<std::vector<int>>();
}
data->push_back(std::forward<T>(resource));
}
// 効率的なデータ転送
std::vector<int> extractData() {
if (!data) {
return {};
}
return std::move(*data);
}
};
- パフォーマンス最適化のテクニック
#include <string>
#include <vector>
class StringProcessor {
public:
// 文字列連結の最適化
static std::string concatenateEfficiently(
const std::vector<std::string>& strings) {
// 必要なキャパシティを事前計算
size_t totalLength = std::accumulate(strings.begin(), strings.end(),
size_t{0}, [](size_t sum, const std::string& str) {
return sum + str.length();
});
std::string result;
result.reserve(totalLength);
// 効率的な追加
for (const auto& str : strings) {
result += str;
}
return result;
}
// 文字列の効率的な処理
static std::vector<std::string> splitString(std::string str, char delim) {
std::vector<std::string> result;
size_t start = 0;
size_t end = str.find(delim);
while (end != std::string::npos) {
// ムーブを使用した効率的な部分文字列の抽出
result.push_back(str.substr(start, end - start));
start = end + 1;
end = str.find(delim, start);
}
if (start < str.length()) {
result.push_back(str.substr(start));
}
return result;
}
};
- 最新のC++機能の活用
#include <optional>
#include <variant>
class ModernFeatures {
public:
// std::optionalの使用
std::optional<int> findValue(const std::vector<int>& data, int target) {
auto it = std::find(data.begin(), data.end(), target);
if (it != data.end()) {
return *it;
}
return std::nullopt;
}
// std::variantの活用
using Variable = std::variant<int, double, std::string>;
std::string formatVariable(const Variable& var) {
return std::visit([](const auto& value) -> std::string {
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, std::string>) {
return "\"" + value + "\"";
} else {
return std::to_string(value);
}
}, var);
}
// 構造化バインディングの活用
template<typename Map>
void processMap(Map& map) {
for (const auto& [key, value] : map) {
std::cout << formatVariable(key) << ": "
<< formatVariable(value) << std::endl;
}
}
};
これらのサンプルコードは、モダンC++の機能を活用した効率的なコーディングパターンを示しています。特に、パフォーマンスの最適化とコードの可読性の両立に焦点を当てています。
実践的なエラーハンドリング手法
効果的なエラーハンドリングは、堅牢なC++プログラムを作成する上で不可欠です。ここでは、実践的なエラーハンドリング手法と、その実装パターンを解説します。
例外処理の実装パターンと使用例
- カスタム例外クラスの設計
#include <stdexcept>
#include <string>
#include <sstream>
// 基底例外クラス
class ApplicationError : public std::runtime_error {
protected:
int errorCode;
std::string details;
public:
ApplicationError(const std::string& message, int code)
: std::runtime_error(message)
, errorCode(code) {}
int getErrorCode() const noexcept { return errorCode; }
void addDetails(const std::string& detail) { details += detail; }
const std::string& getDetails() const noexcept { return details; }
};
// 具体的な例外クラス
class DatabaseError : public ApplicationError {
public:
enum class ErrorCode {
ConnectionFailed = 1001,
QueryFailed = 1002,
TransactionFailed = 1003
};
DatabaseError(const std::string& message, ErrorCode code)
: ApplicationError(message, static_cast<int>(code)) {}
};
class NetworkError : public ApplicationError {
public:
enum class ErrorCode {
ConnectionFailed = 2001,
Timeout = 2002,
InvalidResponse = 2003
};
NetworkError(const std::string& message, ErrorCode code)
: ApplicationError(message, static_cast<int>(code)) {}
};
- 例外安全な関数の実装
class ResourceManager {
private:
std::unique_ptr<Resource> resource;
std::mutex mutex;
public:
// 強い例外保証を提供する関数
void updateResource(const std::string& data) {
// 一時オブジェクトを作成
auto tempResource = std::make_unique<Resource>();
try {
tempResource->initialize(data);
// 成功した場合のみ、リソースを更新
std::lock_guard<std::mutex> lock(mutex);
resource = std::move(tempResource);
}
catch (const std::exception& e) {
// エラーログの記録
std::cerr << "Resource update failed: " << e.what() << std::endl;
throw; // 例外を再送出
}
}
// 基本例外保証を提供する関数
void processResource() noexcept(false) {
std::lock_guard<std::mutex> lock(mutex);
if (!resource) {
throw std::runtime_error("Resource not initialized");
}
try {
resource->process();
}
catch (...) {
// クリーンアップ処理を実行
resource->cleanup();
throw; // 例外を再送出
}
}
};
エラー状態の適切な伝播方法
- エラー状態のカプセル化
#include <optional>
#include <variant>
#include <system_error>
// Result型の実装
template<typename T, typename E = std::error_code>
class Result {
private:
std::variant<T, E> data;
public:
Result(const T& value) : data(value) {}
Result(const E& error) : data(error) {}
bool isSuccess() const {
return std::holds_alternative<T>(data);
}
const T& value() const {
if (!isSuccess()) {
throw std::runtime_error("Accessing error result");
}
return std::get<T>(data);
}
const E& error() const {
if (isSuccess()) {
throw std::runtime_error("Accessing successful result");
}
return std::get<E>(data);
}
// モナド的な操作
template<typename F>
auto map(F&& f) -> Result<decltype(f(std::declval<T>())), E> {
if (isSuccess()) {
try {
return Result<decltype(f(std::declval<T>())), E>(
f(std::get<T>(data)));
}
catch (const std::exception& e) {
return Result<decltype(f(std::declval<T>())), E>(
std::error_code());
}
}
return Result<decltype(f(std::declval<T>())), E>(std::get<E>(data));
}
};
- エラー処理パターンの実装
class ErrorHandler {
public:
// 階層的なエラー処理
template<typename Operation>
void executeWithRetry(Operation&& op, int maxRetries = 3) {
int attempts = 0;
std::exception_ptr lastError;
while (attempts < maxRetries) {
try {
op();
return; // 成功した場合
}
catch (const NetworkError& e) {
lastError = std::current_exception();
// ネットワークエラーは再試行
std::this_thread::sleep_for(
std::chrono::seconds(attempts + 1));
}
catch (const std::exception& e) {
// その他のエラーは即座に再送出
throw;
}
++attempts;
}
// 最大再試行回数を超えた場合
std::rethrow_exception(lastError);
}
// エラーの変換と集約
template<typename T>
std::vector<T> executeAll(
const std::vector<std::function<T()>>& operations) {
std::vector<T> results;
std::vector<std::exception_ptr> errors;
for (const auto& op : operations) {
try {
results.push_back(op());
}
catch (...) {
errors.push_back(std::current_exception());
}
}
// エラーが発生した場合の処理
if (!errors.empty()) {
std::ostringstream oss;
oss << errors.size() << " operations failed";
throw std::runtime_error(oss.str());
}
return results;
}
};
- エラーログとレポート機能
class ErrorReporter {
private:
std::ofstream logFile;
std::mutex logMutex;
public:
// 構造化されたエラーログ
void logError(const std::exception& e,
const std::source_location& location =
std::source_location::current()) {
std::lock_guard<std::mutex> lock(logMutex);
logFile << "Error at " << location.file_name() << ":"
<< location.line() << "\n"
<< "Function: " << location.function_name() << "\n"
<< "Message: " << e.what() << "\n\n";
}
// スタックトレース付きのエラーレポート
template<typename E>
void reportError(const E& error) {
try {
throw error; // スタックトレースを取得するため再送出
}
catch (const std::exception& e) {
std::lock_guard<std::mutex> lock(logMutex);
logFile << "Exception caught: " << typeid(e).name() << "\n"
<< "Message: " << e.what() << "\n"
<< "Stack trace:\n";
// プラットフォーム依存のスタックトレース取得
// Windows: CaptureStackBackTrace
// Unix: backtrace
}
}
};
これらのサンプルコードは、実践的なエラーハンドリング手法を示しています。特に、例外安全性の確保、エラーの適切な伝播、そしてエラー情報の効果的な管理に焦点を当てています。
テスト駆動開発のためのサンプルコード
テスト駆動開発(TDD)は、品質の高いC++コードを作成するための効果的な手法です。ここでは、実践的なテストの実装方法とテスタブルなコード設計について解説します。
単体テストの実装テクニック
- テスト用フレームワークの基本実装
#include <functional>
#include <vector>
#include <string>
#include <iostream>
#include <stdexcept>
class TestFramework {
private:
struct TestCase {
std::string name;
std::function<void()> test;
};
std::vector<TestCase> tests;
public:
void addTest(const std::string& name, std::function<void()> test) {
tests.push_back({name, std::move(test)});
}
// テストの実行
void runTests() {
size_t passed = 0;
std::vector<std::string> failures;
for (const auto& test : tests) {
try {
test.test();
std::cout << "✓ " << test.name << " passed\n";
++passed;
}
catch (const std::exception& e) {
failures.push_back(test.name + ": " + e.what());
}
}
// テスト結果の表示
std::cout << "\nResults:\n"
<< passed << "/" << tests.size() << " tests passed\n\n";
if (!failures.empty()) {
std::cout << "Failures:\n";
for (const auto& failure : failures) {
std::cout << "✗ " << failure << "\n";
}
}
}
};
// アサーション関数
template<typename T>
void assertEqual(const T& expected, const T& actual,
const std::string& message = "") {
if (expected != actual) {
throw std::runtime_error(
"Assertion failed: expected " + std::to_string(expected) +
" but got " + std::to_string(actual) +
(message.empty() ? "" : " - " + message));
}
}
template<>
void assertEqual<std::string>(
const std::string& expected,
const std::string& actual,
const std::string& message) {
if (expected != actual) {
throw std::runtime_error(
"Assertion failed: expected '" + expected +
"' but got '" + actual + "'" +
(message.empty() ? "" : " - " + message));
}
}
- テスト対象クラスの実装例
// テスト対象のクラス
class Calculator {
public:
virtual ~Calculator() = default;
virtual int add(int a, int b) const { return a + b; }
virtual int subtract(int a, int b) const { return a - b; }
virtual int multiply(int a, int b) const { return a * b; }
virtual int divide(int a, int b) const {
if (b == 0) throw std::invalid_argument("Division by zero");
return a / b;
}
};
// テストケースの実装
void testCalculator() {
TestFramework tests;
Calculator calc;
// 加算のテスト
tests.addTest("Addition test", [&] {
assertEqual(4, calc.add(2, 2), "2 + 2 should be 4");
assertEqual(0, calc.add(-1, 1), "-1 + 1 should be 0");
});
// 除算のテスト(例外処理を含む)
tests.addTest("Division test", [&] {
assertEqual(2, calc.divide(4, 2), "4 / 2 should be 2");
try {
calc.divide(1, 0);
throw std::runtime_error("Expected division by zero exception");
}
catch (const std::invalid_argument&) {
// 期待される例外
}
});
tests.runTests();
}
モックオブジェクトを使用したテスト
- モックオブジェクトの基本実装
// データベースインターフェース
class IDatabase {
public:
virtual ~IDatabase() = default;
virtual bool connect() = 0;
virtual bool disconnect() = 0;
virtual bool executeQuery(const std::string& query) = 0;
};
// モックデータベース
class MockDatabase : public IDatabase {
private:
bool isConnected = false;
std::vector<std::string> executedQueries;
public:
bool connect() override {
isConnected = true;
return true;
}
bool disconnect() override {
isConnected = false;
return true;
}
bool executeQuery(const std::string& query) override {
if (!isConnected) return false;
executedQueries.push_back(query);
return true;
}
// テスト用のヘルパーメソッド
bool wasQueryExecuted(const std::string& query) const {
return std::find(executedQueries.begin(),
executedQueries.end(),
query) != executedQueries.end();
}
size_t getQueryCount() const {
return executedQueries.size();
}
};
// テスト対象のクラス
class UserService {
private:
IDatabase& db;
public:
explicit UserService(IDatabase& database) : db(database) {}
bool createUser(const std::string& username) {
if (!db.connect()) return false;
bool result = db.executeQuery(
"INSERT INTO users (username) VALUES ('" + username + "')");
db.disconnect();
return result;
}
};
- モックを使用したテストの実装
void testUserService() {
TestFramework tests;
tests.addTest("User creation test", [] {
MockDatabase mockDb;
UserService service(mockDb);
// ユーザー作成のテスト
bool result = service.createUser("testuser");
assertEqual(true, result, "User creation should succeed");
assertEqual(true, mockDb.wasQueryExecuted(
"INSERT INTO users (username) VALUES ('testuser')"),
"Expected query was not executed");
});
tests.runTests();
}
- テスタブルなコード設計
// 依存性注入を使用したテスタブルな設計
class ITimeProvider {
public:
virtual ~ITimeProvider() = default;
virtual std::time_t getCurrentTime() = 0;
};
class RealTimeProvider : public ITimeProvider {
public:
std::time_t getCurrentTime() override {
return std::time(nullptr);
}
};
class MockTimeProvider : public ITimeProvider {
private:
std::time_t fixedTime;
public:
explicit MockTimeProvider(std::time_t time) : fixedTime(time) {}
std::time_t getCurrentTime() override { return fixedTime; }
};
class ExpirationChecker {
private:
ITimeProvider& timeProvider;
public:
explicit ExpirationChecker(ITimeProvider& provider)
: timeProvider(provider) {}
bool isExpired(std::time_t expirationTime) {
return timeProvider.getCurrentTime() >= expirationTime;
}
};
// テストの実装
void testExpirationChecker() {
TestFramework tests;
tests.addTest("Expiration test", [] {
std::time_t fixedTime = 1000;
MockTimeProvider mockTime(fixedTime);
ExpirationChecker checker(mockTime);
assertEqual(false, checker.isExpired(1001),
"Should not be expired");
assertEqual(true, checker.isExpired(1000),
"Should be expired");
assertEqual(true, checker.isExpired(999),
"Should be expired");
});
tests.runTests();
}
これらのサンプルコードは、テスト駆動開発の実践的なパターンを示しています。特に、テスタブルなコード設計、モックオブジェクトの活用、そして効果的なテストケースの実装に焦点を当てています。