vectorによる2次元配列の基礎知識
従来の配列との違いを理解する
C++での2次元配列の実装方法には、従来の固定長配列とvectorを使用する方法があります。それぞれの特徴を比較しながら、vectorを使用することのメリットを解説します。
従来の固定長配列による実装:
// 固定長の2次元配列(5x4の行列) int traditional_array[5][4]; // 初期化時にサイズを決定する必要がある const int rows = 5; const int cols = 4; int dynamic_array[rows][cols]; // C99以降でのみ有効
vectorによる実装:
// 可変長の2次元配列
std::vector<std::vector<int>> vector_array;
// サイズは実行時に決定可能
vector_array.resize(5); // 5行に拡張
for(auto& row : vector_array) {
row.resize(4); // 各行を4列に拡張
}
従来の配列と比較した主な違いは以下の通りです:
| 特徴 | 従来の配列 | vector |
|---|---|---|
| サイズの可変性 | 固定長 | 動的に変更可能 |
| メモリ割り当て | コンパイル時/スタック | 実行時/ヒープ |
| 境界チェック | なし | at()メソッドで可能 |
| メモリ管理 | 手動 | 自動 |
| 演算子のサポート | 限定的 | 豊富 |
vectorの特徴とメリット
vectorを使用した2次元配列には、以下のような特徴とメリットがあります:
- 動的なサイズ変更
// 行の追加が容易
vector_array.push_back(std::vector<int>(4, 0)); // 新しい行を追加
// 列の追加も可能
for(auto& row : vector_array) {
row.push_back(0); // 各行に新しい列を追加
}
- 安全性の向上
// 範囲外アクセスのチェックが可能
try {
int value = vector_array.at(5).at(3); // 範囲外アクセスは例外をスロー
} catch(const std::out_of_range& e) {
std::cerr << "範囲外アクセスが検出されました: " << e.what() << std::endl;
}
- STLアルゴリズムとの親和性
// アルゴリズムの活用例 #include <algorithm> // 特定の行をソート std::sort(vector_array[0].begin(), vector_array[0].end()); // 行全体を逆順にする std::reverse(vector_array.begin(), vector_array.end());
- メモリ管理の自動化
- メモリの自動確保と解放
- メモリリークの防止
- 効率的なメモリ使用のためのcapacity管理
- イテレータのサポート
// 範囲ベースforループの使用が可能
for(const auto& row : vector_array) {
for(const auto& element : row) {
std::cout << element << ' ';
}
std::cout << std::endl;
}
これらの特徴により、vectorを使用した2次元配列は、特に以下のような場面で威力を発揮します:
- データサイズが動的に変化する行列計算
- 大規模なデータ構造の管理
- エラーハンドリングが重要な業務アプリケーション
- 要素の追加・削除が頻繁に発生するデータ処理
次のセクションでは、これらの特徴を活かした具体的な実装方法について解説していきます。
2次元配列の実装方法
vector>による実装手順
2次元配列の実装には、主に以下の3つのアプローチがあります。それぞれの特徴を理解し、用途に応じて適切な方法を選択することが重要です。
- 基本的な実装方法
#include <vector>
#include <iostream>
// 基本的な2次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix(3, std::vector<int>(4, 0));
// 利用例
void basic_usage() {
// 値の代入
matrix[0][0] = 1;
// 値の取得
int value = matrix[0][0];
// サイズの取得
size_t rows = matrix.size(); // 行数
size_t cols = matrix[0].size(); // 列数
// 全要素の出力
for(size_t i = 0; i < rows; ++i) {
for(size_t j = 0; j < cols; ++j) {
std::cout << matrix[i][j] << " ";
}
std::cout << std::endl;
}
}
- テンプレートを使用した汎用的な実装
template<typename T>
class Matrix2D {
private:
std::vector<std::vector<T>> data;
size_t rows;
size_t cols;
public:
Matrix2D(size_t r, size_t c, const T& default_value = T())
: data(r, std::vector<T>(c, default_value))
, rows(r)
, cols(c) {}
// 要素へのアクセス
T& at(size_t r, size_t c) {
return data.at(r).at(c);
}
// const参照を返すオーバーロード
const T& at(size_t r, size_t c) const {
return data.at(r).at(c);
}
// サイズ取得
size_t getRows() const { return rows; }
size_t getCols() const { return cols; }
};
// 使用例
void template_usage() {
Matrix2D<int> mat(3, 4, 0);
mat.at(0, 0) = 1;
}
- イテレータを活用した実装
template<typename T>
class Matrix2DIterator {
private:
std::vector<std::vector<T>>& matrix;
size_t current_row;
size_t current_col;
public:
Matrix2DIterator(std::vector<std::vector<T>>& mat, size_t row = 0, size_t col = 0)
: matrix(mat), current_row(row), current_col(col) {}
T& operator*() {
return matrix[current_row][current_col];
}
Matrix2DIterator& operator++() {
if (++current_col >= matrix[current_row].size()) {
current_col = 0;
++current_row;
}
return *this;
}
};
メモリ効率を考慮した実装方法
メモリ効率を重視する場合、以下のような実装テクニックが有効です:
- 1次元配列による2次元配列の表現
template<typename T>
class EfficientMatrix2D {
private:
std::vector<T> data;
size_t rows;
size_t cols;
public:
EfficientMatrix2D(size_t r, size_t c, const T& default_value = T())
: data(r * c, default_value)
, rows(r)
, cols(c) {}
// 要素へのアクセス
T& at(size_t r, size_t c) {
return data.at(r * cols + c);
}
const T& at(size_t r, size_t c) const {
return data.at(r * cols + c);
}
};
- メモリアロケーションの最適化
// 事前にメモリを確保
std::vector<std::vector<int>> optimized_matrix;
optimized_matrix.reserve(1000); // 行数分の領域を予約
for(auto& row : optimized_matrix) {
row.reserve(1000); // 各行の列数分の領域を予約
}
実装時の重要なポイント:
| 考慮点 | 推奨される対応 |
|---|---|
| メモリ断片化 | 1次元配列による実装を検討 |
| キャッシュ効率 | データのメモリレイアウトを最適化 |
| 拡張性 | テンプレートを活用した汎用的な設計 |
| 境界チェック | at()メソッドの実装による安全性確保 |
メモリ効率を高めるためのベストプラクティス:
- 必要なサイズを事前に把握
- reserve()を使用して適切なメモリ確保
- 不要なメモリの再割り当てを防止
- メモリアライメントの考慮
// アライメントを考慮したデータ構造
struct alignas(64) AlignedData {
std::vector<int> data;
};
- コピーの最小化
// 参照やムーブセマンティクスの活用
void process_matrix(const std::vector<std::vector<int>>& matrix) {
// データのコピーを避けて処理
}
これらの実装方法は、用途や要件に応じて適切に選択することが重要です。次のセクションでは、これらの実装に対する効率的な初期化とメモリ割り当ての最適化について詳しく解説します。
初期化とメモリ割り当ての最適化
効率的な初期化テクニック
2次元vectorの初期化は、パフォーマンスに大きな影響を与える重要な要素です。以下に、様々な初期化テクニックとそれぞれの特徴を解説します。
- コンストラクタを使用した初期化
// サイズと初期値を指定した初期化
std::vector<std::vector<int>> matrix1(3, std::vector<int>(4, 0));
// イニシャライザリストを使用した初期化
std::vector<std::vector<int>> matrix2 = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 初期化時のパフォーマンス比較
void compare_initialization() {
const size_t rows = 1000;
const size_t cols = 1000;
// 方法1: 二段階の初期化
auto start = std::chrono::high_resolution_clock::now();
std::vector<std::vector<int>> matrix_a;
matrix_a.resize(rows);
for(auto& row : matrix_a) {
row.resize(cols);
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff1 = end - start;
// 方法2: コンストラクタでの一括初期化
start = std::chrono::high_resolution_clock::now();
std::vector<std::vector<int>> matrix_b(rows, std::vector<int>(cols));
end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff2 = end - start;
std::cout << "二段階初期化: " << diff1.count() << "秒\n";
std::cout << "一括初期化: " << diff2.count() << "秒\n";
}
- カスタム初期化関数の実装
template<typename T>
class Matrix2D {
public:
// 効率的な初期化のためのヘルパー関数
static std::vector<std::vector<T>> create_matrix(
size_t rows,
size_t cols,
const std::function<T(size_t, size_t)>& initializer
) {
std::vector<std::vector<T>> matrix(rows);
for(size_t i = 0; i < rows; ++i) {
matrix[i].reserve(cols); // メモリの事前確保
for(size_t j = 0; j < cols; ++j) {
matrix[i].push_back(initializer(i, j));
}
}
return matrix;
}
};
// 使用例
auto matrix = Matrix2D<int>::create_matrix(3, 4,
[](size_t i, size_t j) { return i * j; });
メモリアロケーションの最適化方法
メモリアロケーションを最適化することで、パフォーマンスを大幅に向上させることができます。
- 事前メモリ確保とキャパシティ管理
class OptimizedMatrix {
private:
std::vector<std::vector<int>> data;
void optimize_capacity(size_t rows, size_t cols) {
// 行数分のメモリを事前確保
data.reserve(rows);
// 各行の列数分のメモリを事前確保
for(auto& row : data) {
row.reserve(cols);
}
}
public:
OptimizedMatrix(size_t rows, size_t cols) {
optimize_capacity(rows, cols);
data.resize(rows, std::vector<int>(cols));
}
// キャパシティ情報の取得
void print_capacity_info() const {
std::cout << "行のcapacity: " << data.capacity() << "\n";
if(!data.empty()) {
std::cout << "列のcapacity: " << data[0].capacity() << "\n";
}
}
};
- メモリアロケーションの最小化
パフォーマンス比較のためのベンチマーク関数:
void benchmark_allocation_strategies() {
const size_t rows = 10000;
const size_t cols = 10000;
// 1. 最適化なし
auto start = std::chrono::high_resolution_clock::now();
std::vector<std::vector<int>> unoptimized;
for(size_t i = 0; i < rows; ++i) {
unoptimized.push_back(std::vector<int>(cols));
}
auto end = std::chrono::high_resolution_clock::now();
auto time1 = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// 2. reserve使用
start = std::chrono::high_resolution_clock::now();
std::vector<std::vector<int>> optimized;
optimized.reserve(rows);
for(size_t i = 0; i < rows; ++i) {
optimized.emplace_back(cols);
}
end = std::chrono::high_resolution_clock::now();
auto time2 = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "最適化なし: " << time1.count() << "ms\n";
std::cout << "reserve使用: " << time2.count() << "ms\n";
}
メモリ最適化のためのベストプラクティス:
| 最適化手法 | 効果 | 使用シーン |
|---|---|---|
| reserve() | メモリ再割り当ての回数を削減 | サイズが予測可能な場合 |
| shrink_to_fit() | 不要なメモリを解放 | データ処理完了後 |
| emplace_back() | コピー/ムーブの回数を削減 | 要素の追加時 |
| メモリプール | アロケーションのオーバーヘッドを削減 | 頻繁な確保/解放がある場合 |
これらの最適化テクニックを適切に組み合わせることで、2次元vectorのパフォーマンスを最大限に引き出すことができます。次のセクションでは、最適化された2次元配列に対する効率的な操作テクニックについて解説します。
2次元配列の操作テクニック
要素のアクセスと変更方法
2次元vectorの要素に対する効率的なアクセスと操作方法について解説します。
- 基本的なアクセス方法
#include <vector>
#include <iostream>
class Matrix2DOperations {
private:
std::vector<std::vector<int>> matrix;
public:
Matrix2DOperations(size_t rows, size_t cols)
: matrix(rows, std::vector<int>(cols)) {}
// 安全な要素アクセス
int& at(size_t row, size_t col) {
return matrix.at(row).at(col);
}
// 高速な要素アクセス(境界チェックなし)
int& operator()(size_t row, size_t col) {
return matrix[row][col];
}
// 範囲チェック付きの要素設定
bool set_value(size_t row, size_t col, int value) {
if (row < matrix.size() && col < matrix[0].size()) {
matrix[row][col] = value;
return true;
}
return false;
}
};
- 効率的な要素走査
// 行優先アクセス(キャッシュフレンドリー)
void traverse_row_major(std::vector<std::vector<int>>& matrix) {
for(size_t i = 0; i < matrix.size(); ++i) {
for(size_t j = 0; j < matrix[i].size(); ++j) {
matrix[i][j] = i * j; // 任意の操作
}
}
}
// 列優先アクセス(特定の用途で必要な場合)
void traverse_column_major(std::vector<std::vector<int>>& matrix) {
for(size_t j = 0; j < matrix[0].size(); ++j) {
for(size_t i = 0; i < matrix.size(); ++i) {
matrix[i][j] = i * j; // 任意の操作
}
}
}
- イテレータを使用したアクセス
void iterator_access(std::vector<std::vector<int>>& matrix) {
// 範囲ベースforループ
for(auto& row : matrix) {
for(auto& element : row) {
element *= 2; // 各要素を2倍
}
}
// 明示的なイテレータ
for(auto it = matrix.begin(); it != matrix.end(); ++it) {
for(auto jt = it->begin(); jt != it->end(); ++jt) {
*jt += 1; // 各要素に1を加算
}
}
}
行と列の追加・削除の実装
効率的な行と列の操作方法について説明します。
- 行の操作
class MatrixRowOperations {
public:
// 行の追加
static void add_row(std::vector<std::vector<int>>& matrix,
const std::vector<int>& new_row) {
if (!matrix.empty() && new_row.size() == matrix[0].size()) {
matrix.push_back(new_row);
}
}
// 行の削除
static void remove_row(std::vector<std::vector<int>>& matrix,
size_t row_index) {
if (row_index < matrix.size()) {
matrix.erase(matrix.begin() + row_index);
}
}
// 行の挿入
static void insert_row(std::vector<std::vector<int>>& matrix,
size_t position,
const std::vector<int>& new_row) {
if (position <= matrix.size() &&
(matrix.empty() || new_row.size() == matrix[0].size())) {
matrix.insert(matrix.begin() + position, new_row);
}
}
};
- 列の操作
class MatrixColumnOperations {
public:
// 列の追加
static void add_column(std::vector<std::vector<int>>& matrix,
const std::vector<int>& new_column) {
if (new_column.size() == matrix.size()) {
for(size_t i = 0; i < matrix.size(); ++i) {
matrix[i].push_back(new_column[i]);
}
}
}
// 列の削除
static void remove_column(std::vector<std::vector<int>>& matrix,
size_t col_index) {
for(auto& row : matrix) {
if (col_index < row.size()) {
row.erase(row.begin() + col_index);
}
}
}
// 列の挿入
static void insert_column(std::vector<std::vector<int>>& matrix,
size_t position,
const std::vector<int>& new_column) {
if (new_column.size() == matrix.size()) {
for(size_t i = 0; i < matrix.size(); ++i) {
matrix[i].insert(matrix[i].begin() + position,
new_column[i]);
}
}
}
};
- 効率的な操作のためのユーティリティ関数
class MatrixUtils {
public:
// 行と列の入れ替え(転置)
static std::vector<std::vector<int>> transpose(
const std::vector<std::vector<int>>& matrix) {
if (matrix.empty()) return {};
std::vector<std::vector<int>> result(
matrix[0].size(),
std::vector<int>(matrix.size())
);
for(size_t i = 0; i < matrix.size(); ++i) {
for(size_t j = 0; j < matrix[i].size(); ++j) {
result[j][i] = matrix[i][j];
}
}
return result;
}
// 部分行列の抽出
static std::vector<std::vector<int>> submatrix(
const std::vector<std::vector<int>>& matrix,
size_t start_row, size_t end_row,
size_t start_col, size_t end_col) {
std::vector<std::vector<int>> result;
for(size_t i = start_row; i < end_row && i < matrix.size(); ++i) {
std::vector<int> row;
for(size_t j = start_col;
j < end_col && j < matrix[i].size();
++j) {
row.push_back(matrix[i][j]);
}
if (!row.empty()) {
result.push_back(row);
}
}
return result;
}
};
これらの操作を効率的に実装することで、2次元vectorの操作に関する様々なニーズに対応できます。次のセクションでは、これらの操作をより効率的に行うためのパフォーマンス最適化について解説します。
パフォーマンス最適化のベストプラクティス
メモリアクセスの効率化手法
2次元vectorのパフォーマンスを最大限に引き出すための最適化テクニックを解説します。
- データレイアウトの最適化
// 効率的なデータ構造
class OptimizedMatrix {
private:
std::vector<int> data;
size_t rows;
size_t cols;
public:
OptimizedMatrix(size_t r, size_t c)
: data(r * c), rows(r), cols(c) {}
// 高速なインライン要素アクセス
inline int& at(size_t i, size_t j) {
return data[i * cols + j];
}
// 連続したメモリ領域での操作
void process_all_elements() {
for(auto& element : data) {
// 要素の直接処理
element *= 2;
}
}
};
// パフォーマンス比較
void compare_performance() {
const size_t SIZE = 1000;
// 従来の実装
std::vector<std::vector<int>> traditional(SIZE, std::vector<int>(SIZE));
// 最適化版
OptimizedMatrix optimized(SIZE, SIZE);
auto start = std::chrono::high_resolution_clock::now();
// 従来版の処理
for(auto& row : traditional) {
for(auto& element : row) {
element *= 2;
}
}
auto mid = std::chrono::high_resolution_clock::now();
// 最適化版の処理
optimized.process_all_elements();
auto end = std::chrono::high_resolution_clock::now();
std::cout << "従来版: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
mid - start).count() << "ms\n";
std::cout << "最適化版: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
end - mid).count() << "ms\n";
}
- SIMD操作の活用
#include <immintrin.h> // AVX2命令用
class SIMDMatrix {
private:
std::vector<int> data;
size_t rows, cols;
public:
// AVX2を使用した高速な処理
void process_with_simd() {
// 32ビット整数8個を同時に処理
const size_t step = 8;
size_t aligned_size = (data.size() / step) * step;
for(size_t i = 0; i < aligned_size; i += step) {
__m256i values = _mm256_loadu_si256(
reinterpret_cast<const __m256i*>(&data[i])
);
values = _mm256_add_epi32(values, _mm256_set1_epi32(1));
_mm256_storeu_si256(
reinterpret_cast<__m256i*>(&data[i]),
values
);
}
// 残りの要素を通常処理
for(size_t i = aligned_size; i < data.size(); ++i) {
data[i] += 1;
}
}
};
キャッシュフレンドリーな実装方法
- キャッシュラインを考慮したデータアクセス
class CacheOptimizedMatrix {
private:
static constexpr size_t CACHE_LINE_SIZE = 64; // 一般的なキャッシュラインサイズ
std::vector<int> data;
size_t rows, cols;
public:
// キャッシュライン境界でのアライメント
void* operator new(size_t size) {
void* ptr = nullptr;
if (posix_memalign(&ptr, CACHE_LINE_SIZE, size) != 0) {
throw std::bad_alloc();
}
return ptr;
}
// キャッシュフレンドリーな走査
void process_cache_friendly() {
const size_t block_size = 16; // ブロックサイズ
for(size_t i = 0; i < rows; i += block_size) {
for(size_t j = 0; j < cols; j += block_size) {
// ブロック単位での処理
for(size_t bi = i;
bi < std::min(i + block_size, rows);
++bi) {
for(size_t bj = j;
bj < std::min(j + block_size, cols);
++bj) {
at(bi, bj) *= 2;
}
}
}
}
}
};
- メモリアクセスパターンの最適化
class AccessPatternOptimized {
public:
// プリフェッチを活用した最適化
static void optimize_access_pattern(std::vector<std::vector<int>>& matrix) {
const size_t rows = matrix.size();
const size_t cols = matrix[0].size();
for(size_t i = 0; i < rows; ++i) {
// 次の行のデータをプリフェッチ
if (i + 1 < rows) {
__builtin_prefetch(&matrix[i + 1][0], 0, 3);
}
for(size_t j = 0; j < cols; ++j) {
matrix[i][j] *= 2;
}
}
}
};
パフォーマンス最適化のためのベストプラクティス:
| 最適化手法 | 効果 | 適用シーン |
|---|---|---|
| 1次元配列化 | キャッシュミス削減 | 大規模データ処理 |
| SIMD活用 | 並列処理による高速化 | 数値計算 |
| ブロック処理 | キャッシュ効率向上 | 行列演算 |
| プリフェッチ | メモリアクセス最適化 | 連続データ処理 |
実装時の注意点:
- メモリアクセスパターン
- 連続したメモリアクセスを優先
- キャッシュラインの境界を意識
- データの局所性
- 時間的局所性の活用
- 空間的局所性の確保
- アライメント
- SIMD操作のための適切なアライメント
- キャッシュライン境界での配置
これらの最適化テクニックを適切に組み合わせることで、2次元vectorの処理性能を大幅に向上させることができます。次のセクションでは、これらの最適化を安全に実装するためのエラーハンドリング手法について解説します。
エラーハンドリングと安全な実装
範囲外アクセスの防止方法
2次元vectorの安全な実装には、適切な範囲チェックとエラーハンドリングが不可欠です。
- 安全なアクセサの実装
#include <stdexcept>
#include <string>
template<typename T>
class SafeMatrix {
private:
std::vector<std::vector<T>> data;
public:
// 範囲チェック付きの要素アクセス
T& at(size_t row, size_t col) {
try {
return data.at(row).at(col);
} catch (const std::out_of_range& e) {
throw std::out_of_range(
"Matrix index out of range: [" +
std::to_string(row) + "][" +
std::to_string(col) + "]"
);
}
}
// const参照を返すオーバーロード
const T& at(size_t row, size_t col) const {
try {
return data.at(row).at(col);
} catch (const std::out_of_range& e) {
throw std::out_of_range(
"Matrix index out of range: [" +
std::to_string(row) + "][" +
std::to_string(col) + "]"
);
}
}
// 境界チェック用のユーティリティメソッド
bool is_valid_index(size_t row, size_t col) const noexcept {
return row < data.size() &&
(data.empty() || col < data[0].size());
}
};
- バウンドチェッカーの実装
template<typename T>
class BoundChecker {
public:
static bool check_bounds(
const std::vector<std::vector<T>>& matrix,
size_t row,
size_t col,
const std::string& operation
) {
if (matrix.empty()) {
throw std::runtime_error(
operation + ": Empty matrix"
);
}
if (row >= matrix.size()) {
throw std::out_of_range(
operation + ": Row index " +
std::to_string(row) + " out of range"
);
}
if (col >= matrix[0].size()) {
throw std::out_of_range(
operation + ": Column index " +
std::to_string(col) + " out of range"
);
}
return true;
}
};
例外処理の実装テクニック
- RAII準拠の実装
template<typename T>
class RAIIMatrix {
private:
std::unique_ptr<std::vector<std::vector<T>>> data;
public:
RAIIMatrix(size_t rows, size_t cols)
: data(std::make_unique<std::vector<std::vector<T>>>()) {
try {
data->resize(rows);
for(auto& row : *data) {
row.resize(cols);
}
} catch (const std::bad_alloc& e) {
throw std::runtime_error(
"Failed to allocate matrix: " +
std::string(e.what())
);
}
}
// ムーブコンストラクタ
RAIIMatrix(RAIIMatrix&& other) noexcept = default;
// ムーブ代入演算子
RAIIMatrix& operator=(RAIIMatrix&& other) noexcept = default;
// コピー操作の禁止
RAIIMatrix(const RAIIMatrix&) = delete;
RAIIMatrix& operator=(const RAIIMatrix&) = delete;
};
- 例外安全な操作の実装
template<typename T>
class ExceptionSafeMatrix {
private:
std::vector<std::vector<T>> data;
public:
// 例外安全な要素追加
void add_row(const std::vector<T>& new_row) {
if (data.empty() || new_row.size() == data[0].size()) {
try {
data.push_back(new_row);
} catch (const std::exception& e) {
// 状態を元に戻す(不要な場合もある)
if (!data.empty()) {
data.pop_back();
}
throw;
}
} else {
throw std::invalid_argument(
"New row size does not match matrix columns"
);
}
}
// トランザクション的な一括更新
void update_block(
size_t start_row,
size_t start_col,
const std::vector<std::vector<T>>& block
) {
// 更新前の状態を保存
auto backup = data;
try {
for(size_t i = 0; i < block.size(); ++i) {
for(size_t j = 0; j < block[i].size(); ++j) {
at(start_row + i, start_col + j) = block[i][j];
}
}
} catch (const std::exception& e) {
// エラー発生時は元の状態に戻す
data = std::move(backup);
throw;
}
}
};
- エラーロギングとデバッグ支援
#include <sstream>
class MatrixErrorHandler {
public:
// エラー情報の詳細な出力
static std::string format_error_info(
const std::exception& e,
const std::string& operation,
size_t row,
size_t col
) {
std::ostringstream oss;
oss << "Error during " << operation << ":\n"
<< "Location: [" << row << "][" << col << "]\n"
<< "Error message: " << e.what() << "\n"
<< "Stack trace: " << get_stack_trace();
return oss.str();
}
private:
static std::string get_stack_trace() {
// プラットフォーム依存のスタックトレース実装
// (実際の実装は環境に応じて変更)
return "Stack trace information";
}
};
安全な実装のためのベストプラクティス:
| 項目 | 実装方針 | 効果 |
|---|---|---|
| 境界チェック | at()メソッドの使用 | 範囲外アクセスの防止 |
| RAII | スマートポインタの活用 | リソースリークの防止 |
| 強い例外保証 | 状態のバックアップと復元 | データの一貫性保持 |
| エラーログ | 詳細な診断情報の提供 | デバッグの効率化 |
実装時の注意点:
- 例外安全性の保証レベル
- 基本保証:処理失敗時もオブジェクトは有効
- 強い保証:処理失敗時は元の状態を維持
- 例外なし保証:例外を投げない
- リソース管理
- スマートポインタの活用
- RAIIパターンの適用
- メモリリークの防止
- エラー報告
- 適切な例外クラスの選択
- 詳細なエラー情報の提供
- ログ機能の実装
これらのエラーハンドリング手法を適切に実装することで、安全で信頼性の高い2次元vector操作を実現できます。次のセクションでは、これらの知識を活かした実践的な活用例について解説します。
実践的な活用例と応用テクニック
ゲーム開発での活用方法
ゲーム開発において2次元vectorは、マップデータの管理やコリジョン検出など、様々な場面で活用されます。
- タイルベースのマップ管理
class GameMap {
private:
std::vector<std::vector<int>> tiles;
static constexpr int EMPTY = 0;
static constexpr int WALL = 1;
static constexpr int ITEM = 2;
public:
GameMap(size_t width, size_t height)
: tiles(height, std::vector<int>(width, EMPTY)) {}
// タイル情報の設定
void set_tile(size_t x, size_t y, int tile_type) {
if (x < tiles[0].size() && y < tiles.size()) {
tiles[y][x] = tile_type;
}
}
// 衝突判定
bool is_walkable(size_t x, size_t y) const {
if (x >= tiles[0].size() || y >= tiles.size()) {
return false;
}
return tiles[y][x] != WALL;
}
// エリア内のアイテム検索
std::vector<std::pair<size_t, size_t>> find_items_in_area(
size_t start_x, size_t start_y,
size_t width, size_t height
) {
std::vector<std::pair<size_t, size_t>> items;
for(size_t y = start_y;
y < std::min(start_y + height, tiles.size());
++y) {
for(size_t x = start_x;
x < std::min(start_x + width, tiles[0].size());
++x) {
if (tiles[y][x] == ITEM) {
items.emplace_back(x, y);
}
}
}
return items;
}
};
- パーティクルシステムの実装
struct Particle {
float x, y; // 位置
float vx, vy; // 速度
float lifetime; // 生存時間
};
class ParticleSystem {
private:
std::vector<std::vector<Particle>> grid;
float cell_size;
public:
ParticleSystem(size_t width, size_t height, float cell_size)
: grid(height, std::vector<Particle>())
, cell_size(cell_size) {
// 各セルの容量を予約
for(auto& row : grid) {
row.reserve(100); // 想定される最大パーティクル数
}
}
// パーティクル追加
void add_particle(const Particle& p) {
size_t grid_x = static_cast<size_t>(p.x / cell_size);
size_t grid_y = static_cast<size_t>(p.y / cell_size);
if (grid_y < grid.size() && grid_x < grid[0].size()) {
grid[grid_y][grid_x].push_back(p);
}
}
// パーティクル更新
void update(float dt) {
std::vector<std::vector<Particle>> new_grid(
grid.size(),
std::vector<Particle>()
);
for(size_t y = 0; y < grid.size(); ++y) {
for(size_t x = 0; x < grid[y].size(); ++x) {
for(auto& p : grid[y][x]) {
// パーティクルの位置更新
p.x += p.vx * dt;
p.y += p.vy * dt;
p.lifetime -= dt;
if (p.lifetime > 0.0f) {
// 新しいグリッド位置を計算
size_t new_x = static_cast<size_t>(p.x / cell_size);
size_t new_y = static_cast<size_t>(p.y / cell_size);
if (new_y < new_grid.size() &&
new_x < new_grid[0].size()) {
new_grid[new_y][new_x].push_back(p);
}
}
}
}
}
grid = std::move(new_grid);
}
};
画像処理での実装例
画像処理においては、2次元vectorを使用してピクセルデータの操作や各種フィルタの実装を行います。
- 画像フィルタの実装
class ImageProcessor {
private:
std::vector<std::vector<uint8_t>> image_data;
public:
// ガウシアンブラーフィルタ
std::vector<std::vector<uint8_t>> apply_gaussian_blur(
const std::vector<std::vector<uint8_t>>& input,
size_t kernel_size
) {
std::vector<std::vector<uint8_t>> output(
input.size(),
std::vector<uint8_t>(input[0].size())
);
// ガウシアンカーネルの生成
std::vector<std::vector<float>> kernel =
create_gaussian_kernel(kernel_size);
int half_size = kernel_size / 2;
// 畳み込み演算
for(size_t y = half_size; y < input.size() - half_size; ++y) {
for(size_t x = half_size; x < input[0].size() - half_size; ++x) {
float sum = 0.0f;
float weight_sum = 0.0f;
for(size_t ky = 0; ky < kernel_size; ++ky) {
for(size_t kx = 0; kx < kernel_size; ++kx) {
size_t img_y = y + ky - half_size;
size_t img_x = x + kx - half_size;
float weight = kernel[ky][kx];
sum += input[img_y][img_x] * weight;
weight_sum += weight;
}
}
output[y][x] = static_cast<uint8_t>(sum / weight_sum);
}
}
return output;
}
// エッジ検出フィルタ
std::vector<std::vector<uint8_t>> detect_edges(
const std::vector<std::vector<uint8_t>>& input
) {
const std::vector<std::vector<int>> sobel_x = {
{-1, 0, 1},
{-2, 0, 2},
{-1, 0, 1}
};
const std::vector<std::vector<int>> sobel_y = {
{-1, -2, -1},
{0, 0, 0},
{1, 2, 1}
};
std::vector<std::vector<uint8_t>> output(
input.size(),
std::vector<uint8_t>(input[0].size())
);
for(size_t y = 1; y < input.size() - 1; ++y) {
for(size_t x = 1; x < input[0].size() - 1; ++x) {
int gx = 0, gy = 0;
// Sobelフィルタの適用
for(int ky = -1; ky <= 1; ++ky) {
for(int kx = -1; kx <= 1; ++kx) {
int pixel = input[y + ky][x + kx];
gx += pixel * sobel_x[ky + 1][kx + 1];
gy += pixel * sobel_y[ky + 1][kx + 1];
}
}
// エッジの強度を計算
int magnitude = static_cast<int>(
std::sqrt(gx * gx + gy * gy)
);
output[y][x] = static_cast<uint8_t>(
std::min(255, magnitude)
);
}
}
return output;
}
private:
// ガウシアンカーネルの生成
static std::vector<std::vector<float>> create_gaussian_kernel(
size_t size
) {
std::vector<std::vector<float>> kernel(
size,
std::vector<float>(size)
);
float sigma = size / 6.0f;
float sum = 0.0f;
int half_size = size / 2;
for(size_t y = 0; y < size; ++y) {
for(size_t x = 0; x < size; ++x) {
int dx = x - half_size;
int dy = y - half_size;
float value = std::exp(
-(dx * dx + dy * dy) / (2 * sigma * sigma)
);
kernel[y][x] = value;
sum += value;
}
}
// 正規化
for(auto& row : kernel) {
for(float& value : row) {
value /= sum;
}
}
return kernel;
}
};
これらの実装例は、以下のような実践的なシーンで活用できます:
- ゲーム開発での活用
- マップエディタの実装
- コリジョン検出システム
- パーティクルエフェクト
- A*経路探索の実装
- 画像処理での活用
- 画像フィルタの実装
- 画像解析アルゴリズム
- パターン認識
- 画像圧縮・変換
これらの実装例を基に、各自の要件に合わせてカスタマイズすることで、効率的な2次元データ処理を実現できます。