コンパイルと実行
ソースコードをコンパイルして実行可能ファイルを作成し、実行する手順。
基本的なコマンド (g++)
最も一般的なコンパイラの一つであるg++の使用例です。
# ソースファイル (例: main.cpp) をコンパイルし、実行可能ファイル (例: my_program) を生成
g++ main.cpp -o my_program
# C++11 標準を指定してコンパイル
g++ -std=c++11 main.cpp -o my_program
# C++14 標準を指定してコンパイル
g++ -std=c++14 main.cpp -o my_program
# C++17 標準を指定してコンパイル
g++ -std=c++17 main.cpp -o my_program
# C++20 標準を指定してコンパイル
g++ -std=c++20 main.cpp -o my_program
実行
生成された実行可能ファイルを実行します。
# Linux / macOS
./my_program
# Windows (コマンドプロンプト or PowerShell)
.\my_program.exe
主要なコンパイルオプション
| オプション | 説明 | 例 |
|---|---|---|
-o <ファイル名> |
出力ファイル名を指定します。 | g++ main.cpp -o app |
-std=c++XX |
使用するC++標準のバージョンを指定します (例: c++11, c++14, c++17, c++20)。 |
g++ -std=c++17 main.cpp -o app |
-O<レベル> |
最適化レベルを指定します (例: -O0 (最適化なし), -O1, -O2, -O3 (最大最適化), -Os (サイズ優先))。 |
g++ -O2 main.cpp -o app |
-g |
デバッグ情報を実行可能ファイルに含めます (デバッガ GDB などで使用)。 | g++ -g main.cpp -o app_debug |
-Wall |
一般的な警告をすべて有効にします。コードの潜在的な問題を検出するのに役立ちます。 | g++ -Wall main.cpp -o app |
-Wextra |
-Wall では有効にならない追加の警告を有効にします。 |
g++ -Wall -Wextra main.cpp -o app |
-Werror |
すべての警告をエラーとして扱います。警告があるとコンパイルが失敗します。 | g++ -Wall -Werror main.cpp -o app |
-I<ディレクトリパス> |
ヘッダーファイルの検索パスを追加します。 | g++ main.cpp -I./include -o app |
-L<ディレクトリパス> |
ライブラリファイルの検索パスを追加します。 | g++ main.cpp -L./lib -o app |
-l<ライブラリ名> |
リンクするライブラリを指定します (例: -lm (数学ライブラリ), -lpthread (スレッドライブラリ))。 |
g++ main.cpp -lm -lpthread -o app |
基本的な構文
C++プログラムの基本的な構造要素。
コメント
// これは単一行コメントです
/*
これは
複数行にわたる
コメントです
*/
ヘッダーインクルード
標準ライブラリや自作のヘッダーファイルを読み込みます。
#include <iostream> // 標準入出力ライブラリ
#include <vector> // vectorコンテナ
#include <string> // stringクラス
#include "my_header.h" // 自作ヘッダーファイル (同じディレクトリにある場合)
名前空間
名前の衝突を避けるために使用します。
#include <iostream>
// 名前空間 std の中の cout を使用
std::cout << "Hello" << std::endl;
// using宣言: 特定の名前だけを現在のスコープに導入
using std::cout;
cout << "World"; // std:: を省略できる
// usingディレクティブ: 名前空間全体を現在のスコープに導入 ( グローバルスコープでの使用は非推奨)
using namespace std;
cout << endl; // std:: を省略できる
// 自前の名前空間
namespace MyLib {
int value = 10;
void print() {
std::cout << "MyLib::print()" << std::endl;
}
}
MyLib::print();
cout << MyLib::value << endl;
main 関数
プログラムのエントリーポイント(開始地点)です。
// 最も基本的な形式 (戻り値 0 は正常終了を示す)
int main() {
// プログラムの処理
return 0;
}
// コマンドライン引数を受け取る形式
// argc: 引数の数 (プログラム名自身を含む)
// argv: 引数の文字列配列 (argv[0]はプログラム名)
int main(int argc, char* argv[]) {
for (int i = 0; i < argc; ++i) {
std::cout << "Argument " << i << ": " << argv[i] << std::endl;
}
return 0;
}
変数とデータ型
データを格納するための基本的な型と変数宣言。
基本的なデータ型
| 型 | 説明 | 例 |
|---|---|---|
int | 整数 (通常 32 ビット) | int age = 30; |
short | 短整数 (通常 16 ビット) | short count = 100; |
long | 長整数 (通常 32 ビットまたは 64 ビット) | long population = 7000000000L; |
long long | さらに長い整数 (通常 64 ビット) | long long distance = 150000000000LL; |
float | 単精度浮動小数点数 | float price = 19.99f; |
double | 倍精度浮動小数点数 | double pi = 3.1415926535; |
long double | 拡張精度浮動小数点数 | long double large_pi = 3.141592653589793238L; |
char | 文字 (通常 8 ビット) | char initial = 'A'; |
wchar_t | ワイド文字 | wchar_t wide_char = L'あ'; |
char16_t | UTF-16 文字 (C++11) | char16_t utf16_char = u'猫'; |
char32_t | UTF-32 文字 (C++11) | char32_t utf32_char = U'犬'; |
bool | ブーリアン (true または false) | bool is_active = true; |
void | 型がないことを示す (主に関数の戻り値やポインタで使用) | void myFunction(); |
修飾子
// 符号付き (デフォルト) / 符号なし
signed int signed_num = -10; // signed は通常省略可能
unsigned int unsigned_num = 10;
// サイズ変更 (short, long)
short int short_int = 10;
long int long_int = 100000L;
long long int long_long_int = 10000000000LL;
auto キーワード (C++11)
コンパイラが初期化式から型を推論します。初期化が必須です。
auto i = 42; // i は int 型
auto d = 3.14; // d は double 型
auto s = "hello"; // s は const char* 型 (文字列リテラル)
auto str = std::string("world"); // str は std::string 型
auto v = std::vector<int>{1, 2, 3}; // v は std::vector<int> 型
// const や参照も推論可能
const auto ci = i; // ci は const int 型
auto& ref_i = i; // ref_i は int& 型
const auto& cref_i = i; // cref_i は const int& 型
定数
値が変更されない変数を定義します。
// const: 実行時に値が決定される定数 (変更不可)
const double PI = 3.14159;
// PI = 3.14; // エラー: const 変数には再代入できない
// constexpr: コンパイル時に値が決定される定数 (C++11)
// コンパイル時計算や定数式が必要な場面で使用可能
constexpr int MAX_SIZE = 100;
constexpr int square(int x) { return x * x; }
int arr[MAX_SIZE]; // OK: 配列サイズに constexpr 変数を使用できる
int val = square(5); // コンパイル時に計算される可能性がある
型エイリアス
既存の型に別名を付けます。using (C++11以降推奨) と typedef があります。
// using (C++11以降推奨)
using Integer = int;
using String = std::string;
using IntVector = std::vector<int>;
using IntVectorIterator = std::vector<int>::iterator;
template<typename T> using Vec = std::vector<T>; // テンプレートエイリアス
Integer count = 10;
String name = "Alice";
Vec<double> data = {1.1, 2.2};
// typedef (旧来の方法)
typedef int Integer_t;
typedef std::string String_t;
typedef std::vector<int> IntVector_t;
Integer_t score = 100;
String_t message = "Hello";
演算子
値の計算や比較などを行う記号。
主要な演算子一覧
| 種類 | 演算子 | 説明 |
|---|---|---|
| 算術演算子 | + | 加算 |
- | 減算 | |
* | 乗算 | |
/ | 除算 | |
% | 剰余 (余り) | |
++ | インクリメント (前置/後置) | |
-- | デクリメント (前置/後置) | |
| 比較演算子 | == | 等しい |
!= | 等しくない | |
< | より小さい | |
> | より大きい | |
<= | 以下 | |
>= | 以上 | |
| 論理演算子 | && | 論理 AND |
|| | 論理 OR | |
! | 論理 NOT | |
| ビット演算子 | & | ビット AND |
| | ビット OR | |
^ | ビット XOR | |
~ | ビット NOT (反転) | |
<< | 左ビットシフト | |
>> | 右ビットシフト | |
| 代入演算子 | = | 代入 |
+= | 加算して代入 (x += y は x = x + y) | |
-= | 減算して代入 | |
*= | 乗算して代入 | |
/= | 除算して代入 | |
%= | 剰余を計算して代入 | |
&= | ビット AND して代入 | |
|= | ビット OR して代入 | |
^= | ビット XOR して代入 | |
<<= | 左シフトして代入 | |
>>= | 右シフトして代入 | |
| 三項演算子 | ? : | 条件演算子 (condition ? value_if_true : value_if_false) |
| sizeof 演算子 | sizeof | 型または変数のサイズ (バイト単位) を返す |
| ポインタ関連 | & | アドレス演算子 (変数のメモリアドレスを取得) |
| ポインタ関連 | * | 間接参照演算子 (ポインタが指す先の値を取得) |
| スコープ解決 | :: | スコープ解決演算子 (名前空間やクラスのメンバにアクセス) |
| その他 | , | カンマ演算子 (式を順番に評価し、最後の式の値を返す) |
| 型キャスト | static_cast, dynamic_cast, const_cast, reinterpret_cast | 型の変換 |
| ヌルポインタ | nullptr | ヌルポインタを表すキーワード (C++11) |
制御フロー
プログラムの実行順序を制御する構文。
if-else 文
int score = 75;
if (score >= 80) {
std::cout << "優" << std::endl;
} else if (score >= 60) {
std::cout << "良" << std::endl;
} else {
std::cout << "可" << std::endl;
}
// C++17: 条件式内で変数を宣言可能
if (auto file = std::fopen("data.txt", "r")) {
// file が nullptr でない場合のみ実行
std::cout << "ファイルを開きました" << std::endl;
std::fclose(file);
} else {
std::cout << "ファイルを開けません" << std::endl;
}
switch 文
int day = 3;
switch (day) {
case 1:
std::cout << "月曜日" << std::endl;
break; // break を忘れないように
case 2:
std::cout << "火曜日" << std::endl;
break;
case 3:
std::cout << "水曜日" << std::endl;
// break; // わざと break しない (フォールスルー)
case 4:
std::cout << "木曜日 (水曜から続くかも)" << std::endl;
break;
default:
std::cout << "週末または無効な曜日" << std::endl;
break;
}
// C++17: 初期化子付き switch 文
switch (auto status = get_status(); status) {
case Status::OK: /* ... */ break;
case Status::Error: /* ... */ break;
}
for ループ
// 通常の for ループ
for (int i = 0; i < 5; ++i) {
std::cout << i << " "; // 0 1 2 3 4
}
std::cout << std::endl;
// 範囲ベース for ループ (C++11)
std::vector<int> nums = {10, 20, 30};
for (int num : nums) {
std::cout << num << " "; // 10 20 30
}
std::cout << std::endl;
// 参照を使って要素を変更可能
for (int& num : nums) {
num *= 2;
}
for (const int& num : nums) { // const参照で読み取り専用
std::cout << num << " "; // 20 40 60
}
std::cout << std::endl;
// C++20: 初期化子付き範囲ベース for
for (std::vector<int> data = {1,2,3}; auto x : data) {
std::cout << x << " ";
}
std::cout << std::endl;
while / do-while ループ
// while ループ (最初に条件を評価)
int count = 0;
while (count < 3) {
std::cout << "while: " << count << std::endl;
count++;
}
// do-while ループ (最後に条件を評価、最低1回は実行)
int num = 5;
do {
std::cout << "do-while: " << num << std::endl;
num--;
} while (num > 5); // 条件は false だが、1回は実行される
break / continue / goto
for (int i = 0; i < 10; ++i) {
if (i == 3) {
continue; // 現在のイテレーションをスキップし、次のイテレーションへ
}
if (i == 7) {
break; // ループ全体を終了
}
std::cout << i << " "; // 0 1 2 4 5 6
}
std::cout << std::endl;
// goto ( 使用は非推奨、スパゲッティコードの原因になりやすい)
int x = 0;
start_loop: // ラベル
if (x < 3) {
std::cout << "goto: " << x << std::endl;
x++;
goto start_loop; // ラベルへジャンプ
}
関数
特定の処理をまとめたコードブロック。
関数宣言と定義
#include <iostream>
#include <string>
// 関数宣言 (プロトタイプ宣言)
int add(int a, int b);
void printMessage(const std::string& msg); // 参照渡し (コピーを防ぐ)
int main() {
int result = add(5, 3);
std::cout << "Sum: " << result << std::endl; // Output: Sum: 8
printMessage("Hello from main!");
return 0;
}
// 関数定義
int add(int a, int b) {
return a + b;
}
void printMessage(const std::string& msg) {
std::cout << "Message: " << msg << std::endl;
}
引数の渡し方
| 渡し方 | 説明 | 例 |
|---|---|---|
| 値渡し (Pass by Value) | 引数のコピーが関数に渡される。関数内での変更は呼び出し元の変数に影響しない。 | void func(int x) { x = 10; } |
| ポインタ渡し (Pass by Pointer) | 引数のメモリアドレスが渡される。関数内でポインタを通じて呼び出し元の変数を変更できる。nullptr の可能性を考慮する必要がある。 |
void func(int* px) { if(px) *px = 10; } |
| 参照渡し (Pass by Reference) | 引数のエイリアス(別名)が渡される。関数内で呼び出し元の変数を直接変更できる。nullptr にはならない。コピーが発生しないため、大きなオブジェクトに効率的。 |
void func(int& rx) { rx = 10; } |
| const 参照渡し | 参照渡しだが、関数内で引数を変更できない。読み取り専用で、コピーのオーバーヘッドを避けたい場合に最適。 | void func(const std::string& str) { std::cout << str; } |
デフォルト引数
関数呼び出し時に引数を省略した場合に使われるデフォルト値を指定できます。
void showInfo(std::string name, int age = 20, std::string city = "Unknown") {
std::cout << "Name: " << name << ", Age: " << age << ", City: " << city << std::endl;
}
int main() {
showInfo("Alice"); // AgeとCityはデフォルト値が使われる -> Name: Alice, Age: 20, City: Unknown
showInfo("Bob", 30); // Cityはデフォルト値 -> Name: Bob, Age: 30, City: Unknown
showInfo("Charlie", 25, "Tokyo"); // 全て指定 -> Name: Charlie, Age: 25, City: Tokyo
return 0;
}
関数オーバーロード
同じ名前で引数の型や数が異なる関数を複数定義できます。
#include <iostream>
#include <string>
void print(int i) {
std::cout << "Integer: " << i << std::endl;
}
void print(double d) {
std::cout << "Double: " << d << std::endl;
}
void print(const std::string& s) {
std::cout << "String: " << s << std::endl;
}
int main() {
print(10); // Integer: 10
print(3.14); // Double: 3.14
print("Hello"); // String: Hello (const char* から std::string への暗黙変換)
return 0;
}
ラムダ式 (C++11)
インラインで無名関数オブジェクトを作成する機能です。アルゴリズムに渡す述語などでよく使われます。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
// 基本的なラムダ式: [] キャプチャリスト, () 引数リスト, {} 本体
auto greet = []() { std::cout << "Hello from Lambda!" << std::endl; };
greet(); // ラムダ式を呼び出し
// 引数を取るラムダ式
auto add = [](int a, int b) -> int { // 戻り値の型推論も可能 (-> int は省略可)
return a + b;
};
std::cout << "Sum: " << add(5, 3) << std::endl; // Sum: 8
// キャプチャ: ラムダ式の外の変数にアクセス
int x = 10;
int y = 20;
// 値キャプチャ [=]: 外の変数をコピーしてキャプチャ
auto lambda1 = [=]() { std::cout << "x+y = " << x + y << std::endl; }; // x=10, y=20 がコピーされる
// 参照キャプチャ [&]: 外の変数を参照でキャプチャ
auto lambda2 = [&]() { x = 100; std::cout << "x=" << x << ", y=" << y << std::endl; }; // x, y への参照
// 特定の変数をキャプチャ
auto lambda3 = [x, &y]() { y = 200; std::cout << "x=" << x << ", y=" << y << std::endl; }; // xはコピー, yは参照
lambda1(); // x+y = 30
lambda2(); // x=100, y=20
std::cout << "After lambda2: x=" << x << std::endl; // x は 100 に変更されている
lambda3(); // x=100, y=200
std::cout << "After lambda3: y=" << y << std::endl; // y は 200 に変更されている
// mutable キーワード: 値キャプチャした変数をラムダ内で変更可能にする
int z = 50;
auto lambda4 = [z]() mutable {
z = 500; // OK (ラムダ内のコピーが変更される)
std::cout << "Inside mutable lambda: z=" << z << std::endl; // Inside mutable lambda: z=500
};
lambda4();
std::cout << "Outside mutable lambda: z=" << z << std::endl; // Outside mutable lambda: z=50 (元の変数は影響されない)
// std::for_each とラムダ式
std::vector<int> numbers = {1, 2, 3, 4, 5};
int sum = 0;
std::for_each(numbers.begin(), numbers.end(), [&sum](int n){ sum += n; });
std::cout << "Vector sum: " << sum << std::endl; // Vector sum: 15
// ジェネリックラムダ (C++14)
auto generic_add = [](auto a, auto b) { return a + b; };
std::cout << generic_add(1, 2) << std::endl; // 3 (int)
std::cout << generic_add(1.5, 2.5) << std::endl; // 4.0 (double)
std::cout << generic_add(std::string("a"), std::string("b")) << std::endl; // "ab" (std::string)
return 0;
}
配列・文字列・ベクター
複数のデータをまとめて扱う基本的な方法。
配列 (C-style Array)
固定サイズの連続したメモリ領域に同じ型の要素を格納します。
#include <iostream>
int main() {
// 宣言と初期化
int scores[5]; // サイズ5のint型配列 (値は未初期化)
double temps[3] = {15.5, 20.1, 18.0}; // 初期化子リストで初期化
char message[] = "Hello"; // 文字列リテラルで初期化 (サイズは自動決定、終端ナル文字'\0'含む)
int counts[4] = {1, 2}; // 要素数より少ない初期化子の場合、残りは0で初期化 {1, 2, 0, 0}
int zeros[5] = {}; // 全要素を0で初期化 (C++11以降)
// 要素へのアクセス (添字は0から)
scores[0] = 100;
scores[1] = 90;
std::cout << "temps[1]: " << temps[1] << std::endl; // temps[1]: 20.1
std::cout << "message: " << message << std::endl; // message: Hello
// 配列のサイズ (sizeof演算子を使用)
int num_scores = sizeof(scores) / sizeof(scores[0]); // 配列全体のバイト数 / 要素1つのバイト数
std::cout << "Number of scores: " << num_scores << std::endl; // Number of scores: 5
// 多次元配列
int matrix[2][3] = { // 2行3列
{1, 2, 3},
{4, 5, 6}
};
std::cout << "matrix[1][1]: " << matrix[1][1] << std::endl; // matrix[1][1]: 5
// 注意点:
// - サイズはコンパイル時に決定される必要がある (定数式)
// - 配列の境界チェックは行われないため、範囲外アクセスに注意
// - 関数に配列を渡すと、通常ポインタに縮退する
return 0;
}
std::string (文字列クラス)
C++標準ライブラリが提供する、動的にサイズ変更可能な文字列クラスです。Cスタイル文字列より安全で高機能です。
#include <iostream>
#include <string>
int main() {
// 宣言と初期化
std::string s1; // 空の文字列
std::string s2 = "Hello";
std::string s3("World");
std::string s4(5, 'A'); // 'A'を5つ繰り返した文字列 "AAAAA"
std::string s5 = s2; // コピー
// 文字列の連結 (+)
std::string greeting = s2 + " " + s3 + "!"; // "Hello World!"
std::cout << greeting << std::endl;
// 文字列の長さ (length() or size())
std::cout << "Length of greeting: " << greeting.length() << std::endl; // 12
// 文字へのアクセス ([ ] or at())
std::cout << "First char: " << greeting[0] << std::endl; // H
// greeting[0] = 'J'; //変更可能
try {
std::cout << "Char at 1: " << greeting.at(1) << std::endl; // e (範囲チェックあり)
// std::cout << greeting.at(100) << std::endl; // 範囲外アクセス -> std::out_of_range 例外
} catch (const std::out_of_range& oor) {
std::cerr << "Out of Range error: " << oor.what() << '\n';
}
// 文字列の比較 (==, !=, <, >, <=, >=)
if (s2 == "Hello") {
std::cout << "s2 is Hello" << std::endl;
}
// 部分文字列 (substr)
std::string sub = greeting.substr(6, 5); // 6番目の位置から5文字 -> "World"
std::cout << "Substring: " << sub << std::endl;
// 文字列の検索 (find)
size_t pos = greeting.find("World"); // "World" が最初に出現する位置
if (pos != std::string::npos) { // npos は見つからなかった場合の値
std::cout << "'World' found at position: " << pos << std::endl; // 6
}
// 文字列の置換 (replace)
greeting.replace(6, 5, "C++"); // 6番目の位置から5文字を "C++" に置換 -> "Hello C++!"
std::cout << greeting << std::endl;
// Cスタイル文字列への変換 (c_str())
const char* c_str = greeting.c_str();
printf("C-style string: %s\n", c_str);
// 文字列が空かどうかのチェック (empty())
if (s1.empty()) {
std::cout << "s1 is empty" << std::endl;
}
return 0;
}
std::vector (動的配列)
要素数を実行時に変更できる動的な配列です。STLコンテナの中で最もよく使われるものの一つです。
#include <iostream>
#include <vector>
#include <numeric> // std::iota
int main() {
// 宣言と初期化
std::vector<int> v1; // 空のint型ベクター
std::vector<double> v2 = {1.1, 2.2, 3.3}; // 初期化子リスト (C++11)
std::vector<std::string> v3(5, "hi"); // "hi" を5つ持つベクター
std::vector<int> v4(10); // サイズ10で、要素はデフォルト値(intなら0)で初期化
// 要素の追加 (push_back) - 末尾に追加
v1.push_back(10);
v1.push_back(20);
v1.push_back(30); // v1: {10, 20, 30}
// 要素へのアクセス ([ ] or at())
std::cout << "v1[0]: " << v1[0] << std::endl; // 10
std::cout << "v2.at(1): " << v2.at(1) << std::endl; // 2.2 (範囲チェックあり)
// v1[0] = 100; // 変更可能
// 要素数 (size())
std::cout << "Size of v1: " << v1.size() << std::endl; // 3
// 容量 (capacity()) - 再確保なしに追加できる要素数
std::cout << "Capacity of v1: " << v1.capacity() << std::endl;
// イテレータを使ったアクセス
std::cout << "v3 elements: ";
for (std::vector<std::string>::iterator it = v3.begin(); it != v3.end(); ++it) {
std::cout << *it << " "; // hi hi hi hi hi
}
std::cout << std::endl;
// 範囲ベースforループ (推奨)
std::cout << "v2 elements: ";
for (const auto& val : v2) {
std::cout << val << " "; // 1.1 2.2 3.3
}
std::cout << std::endl;
// 要素の挿入 (insert)
v1.insert(v1.begin() + 1, 15); // 1番目の位置(0-indexed)に15を挿入 -> {10, 15, 20, 30}
// 要素の削除 (erase)
v1.erase(v1.begin() + 2); // 2番目の要素(20)を削除 -> {10, 15, 30}
// 末尾の要素を削除 (pop_back)
v1.pop_back(); // 末尾(30)を削除 -> {10, 15}
// ベクターのクリア (clear)
v1.clear(); // 全要素を削除 (sizeは0になるが、capacityは変わらない場合がある)
std::cout << "Size of v1 after clear: " << v1.size() << std::endl; // 0
// 空かどうかのチェック (empty())
if (v1.empty()) {
std::cout << "v1 is empty" << std::endl;
}
// サイズ変更 (resize)
v2.resize(5, 0.0); // サイズを5に変更。新しい要素は0.0で初期化 -> {1.1, 2.2, 3.3, 0.0, 0.0}
v2.resize(2); // サイズを2に変更。後ろの要素が削除される -> {1.1, 2.2}
// 容量の予約 (reserve) - メモリ再確保の回数を減らす
std::vector<int> large_vector;
large_vector.reserve(1000); // 少なくとも1000要素分のメモリを確保
std::cout << "Capacity after reserve: " << large_vector.capacity() << std::endl;
// std::array (C++11) - 固定サイズの配列 (vectorとは異なりサイズ変更不可だが、STLの恩恵を受けられる)
std::array<int, 4> arr = {1, 2, 3, 4};
std::cout << "std::array size: " << arr.size() << std::endl; // 4
return 0;
}
ポインタと参照
メモリアドレスを介してデータにアクセスする方法。
ポインタ (Pointer)
変数のメモリアドレスを格納する変数です。
#include <iostream>
int main() {
int value = 10;
int* ptr = nullptr; // ポインタ変数の宣言、nullptrで初期化 (推奨)
// アドレス演算子 (&): 変数のアドレスを取得
ptr = &value;
std::cout << "Value: " << value << std::endl; // 10
std::cout << "Address of value: " << &value << std::endl; // valueのメモリアドレス
std::cout << "Pointer ptr points to: " << ptr << std::endl; // ptrが格納しているアドレス (=valueのアドレス)
// std::cout << "Address of ptr itself: " << &ptr << std::endl; // ptr変数自身のアドレス
// 間接参照演算子 (*): ポインタが指す先の値にアクセス
std::cout << "Value via pointer: " << *ptr << std::endl; // 10
// ポインタを通じて値を変更
*ptr = 20;
std::cout << "New value: " << value << std::endl; // 20 (元の変数 value も変わる)
std::cout << "New value via pointer: " << *ptr << std::endl; // 20
// ポインタ演算 (配列アクセスなどで使用)
int numbers[] = {1, 2, 3};
int* p_num = numbers; // 配列名は先頭要素へのポインタとして扱える
std::cout << "First element: " << *p_num << std::endl; // 1
p_num++; // ポインタを次の要素へ移動 (int型なら4バイトなど、型サイズ分進む)
std::cout << "Second element: " << *p_num << std::endl; // 2
// voidポインタ: 型を問わないポインタ (使用前に適切な型へキャストが必要)
void* void_ptr = &value;
// std::cout << *void_ptr << std::endl; // エラー: voidポインタは直接間接参照できない
int* int_ptr_cast = static_cast<int*>(void_ptr);
std::cout << "Value via void pointer cast: " << *int_ptr_cast << std::endl; // 20
// nullptr チェック
int* null_ptr = nullptr;
if (null_ptr != nullptr) {
// std::cout << *null_ptr << std::endl; // 未定義動作! アクセスしない
} else {
std::cout << "Pointer is null" << std::endl;
}
return 0;
}
参照 (Reference)
既存の変数に対するエイリアス(別名)です。宣言時に必ず初期化が必要で、後から別の変数を指すようには変更できません。
#include <iostream>
#include <string>
void modifyValue(int& ref) { // 参照渡し
ref = 100;
}
int main() {
int original = 50;
// 参照の宣言と初期化
int& ref_original = original; // ref_original は original の別名になる
// int& ref2; // エラー: 参照は宣言時に初期化が必要
// ref_original = another_var; // エラー: 参照は後から別の変数を指せない
std::cout << "Original: " << original << std::endl; // 50
std::cout << "Reference: " << ref_original << std::endl; // 50
// 参照を通じて値を変更
ref_original = 75;
std::cout << "Original after ref modification: " << original << std::endl; // 75
std::cout << "Reference after ref modification: " << ref_original << std::endl; // 75
// 関数に参照渡し
modifyValue(original);
std::cout << "Original after function call: " << original << std::endl; // 100
// const参照: 参照先を変更できない
const int& const_ref = original;
std::cout << "Const reference: " << const_ref << std::endl; // 100
// const_ref = 200; // エラー: const参照を通じては変更できない
// 参照はポインタと異なり、それ自体のアドレスを持つわけではない (通常は)
// std::cout << "Address of original: " << &original << std::endl;
// std::cout << "Address via reference: " << &ref_original << std::endl; // originalと同じアドレス
// 用途:
// - 関数の引数 (コピーを避けたい場合、関数内で値を変更したい場合)
// - 範囲ベースforループでの要素アクセス
// - 演算子のオーバーロード
return 0;
}
動的メモリ確保 (Dynamic Memory Allocation)
プログラム実行中にヒープ領域からメモリを確保・解放します。newで確保し、deleteで解放します。解放漏れ(メモリリーク)に注意が必要です。
#include <iostream>
#include <vector>
int main() {
// 単一オブジェクトの確保と解放
int* dynamic_int = nullptr;
try {
dynamic_int = new int; // int型1つ分のメモリをヒープに確保
*dynamic_int = 42;
std::cout << "Dynamically allocated int: " << *dynamic_int << std::endl;
} catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation failed: " << e.what() << '\n';
// エラー処理
}
delete dynamic_int; // 確保したメモリを解放 (必須!)
dynamic_int = nullptr; // 解放後は nullptr を代入する (ダングリングポインタ防止)
// 配列の確保と解放
int size = 5;
double* dynamic_array = nullptr;
try {
dynamic_array = new double[size]; // double型size個分の連続したメモリを確保
for (int i = 0; i < size; ++i) {
dynamic_array[i] = i * 1.1;
}
std::cout << "Dynamically allocated array [2]: " << dynamic_array[2] << std::endl;
} catch (const std::bad_alloc& e) {
std::cerr << "Array allocation failed: " << e.what() << '\n';
// エラー処理
}
delete[] dynamic_array; // 配列の場合は delete[] で解放 (必須!)
dynamic_array = nullptr;
// 注意点:
// - `new` と `delete`、`new[]` と `delete[]` は必ずペアで使う。
// - 解放を忘れるとメモリリークが発生する。
// - 解放済みのポインタ(ダングリングポインタ)にアクセスすると未定義動作。
// - 例外が発生すると `delete` が実行されずメモリリークする可能性がある。
// => モダンC++では、生ポインタによる直接的なメモリ管理は避け、
// スマートポインタやコンテナ(std::vectorなど)を使うことが強く推奨される。
return 0;
}
スマートポインタ (Smart Pointers) (C++11以降)
RAII (Resource Acquisition Is Initialization) の原則に基づき、ポインタのように振る舞いながら、リソース(特に動的確保されたメモリ)の所有権を管理し、自動的に解放してくれるクラスです。メモリリークやダングリングポインタの問題を大幅に軽減します。
std::unique_ptr<T> (<memory>)
所有権を独占するスマートポインタ。一つの unique_ptr のみが特定のオブジェクトを所有できます。コピーはできず、所有権の移動 (move) のみ可能です。スコープを抜けると自動的にオブジェクトが解放されます。
#include <iostream>
#include <memory> // スマートポインタのヘッダ
#include <vector>
struct MyResource {
MyResource(int id) : id_(id) { std::cout << "Resource " << id_ << " acquired.\n"; }
~MyResource() { std::cout << "Resource " << id_ << " released.\n"; }
void use() { std::cout << "Using resource " << id_ << ".\n"; }
private:
int id_;
};
std::unique_ptr<MyResource> createResource(int id) {
return std::make_unique<MyResource>(id); // 推奨される生成方法 (C++14)
// return std::unique_ptr<MyResource>(new MyResource(id)); // C++11 の場合
}
int main() {
// unique_ptr の生成
std::unique_ptr<int> p1 = std::make_unique<int>(10);
std::unique_ptr<MyResource> r1 = std::make_unique<MyResource>(1);
// 生ポインタのようにアクセス
*p1 = 20;
r1->use(); // アロー演算子も使える
std::cout << "Value pointed by p1: " << *p1 << std::endl;
// コピーはできない
// std::unique_ptr<MyResource> r2 = r1; // コンパイルエラー
// 所有権の移動 (move)
std::unique_ptr<MyResource> r3 = std::move(r1); // r1 は nullptr になり、r3がリソースを所有
if (!r1) {
std::cout << "r1 is now null.\n";
}
r3->use();
// 関数からの返却 (暗黙的なムーブ)
std::unique_ptr<MyResource> r4 = createResource(2);
r4->use();
// 配列の unique_ptr
std::unique_ptr<int[]> p_array = std::make_unique<int[]>(5); // サイズ5のint配列
for(int i=0; i<5; ++i) p_array[i] = i * 10;
std::cout << "p_array[3]: " << p_array[3] << std::endl; // p_array[3]: 30
// コンテナに格納
std::vector<std::unique_ptr<MyResource>> resources;
resources.push_back(std::make_unique<MyResource>(3));
resources.push_back(std::move(r4)); // 所有権をベクターに移す
// resources.push_back(r3); // エラー: コピー不可
std::cout << "Exiting main...\n";
// main関数を抜ける際に、p1, r3, p_array, resources内のr3, r4 が所有する
// リソースが自動的に解放される (デストラクタが呼ばれる)
return 0;
}
// 出力例:
// Resource 1 acquired.
// Using resource 1.
// Value pointed by p1: 20
// r1 is now null.
// Using resource 1.
// Resource 2 acquired.
// Using resource 2.
// p_array[3]: 30
// Resource 3 acquired.
// Exiting main...
// Resource 3 released. // resources のデストラクタから
// Resource 2 released. // resources のデストラクタから
// Resource 1 released. // r3 のデストラクタから
// Resource (array) released // p_array のデストラクタから (見えないが解放される)
// Resource (int) released // p1 のデストラクタから (見えないが解放される)
構造体とクラス
独自のデータ型を定義するための仕組み。
構造体 (struct)
複数の異なる型のデータを一つにまとめるための型。デフォルトのアクセス指定子は public です。
#include <iostream>
#include <string>
struct Point {
// メンバ変数 (デフォルトで public)
double x;
double y;
// メンバ関数 (メソッド)
void print() {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
// コンストラクタ (オブジェクト生成時に呼ばれる)
Point(double x_val = 0.0, double y_val = 0.0) : x(x_val), y(y_val) {
std::cout << "Point constructor called." << std::endl;
}
}; // セミコロンが必要
int main() {
// 構造体のインスタンス化 (オブジェクト生成)
Point p1; // デフォルトコンストラクタ使用 (x=0.0, y=0.0)
Point p2(3.0, 4.0); // 引数付きコンストラクタ使用
Point p3 = {5.0, 6.0}; //集成体初期化 (C++11以降、コンストラクタがあっても可能)
// メンバへのアクセス (.)
p1.x = 1.0;
p1.y = 2.0;
p1.print(); // (1, 2)
p2.print(); // (3, 4)
p3.print(); // (5, 6)
return 0;
}
クラス (class)
構造体とほぼ同じですが、デフォルトのアクセス指定子が private である点が異なります。カプセル化(データ隠蔽)を目的としてよく使われます。
#include <iostream>
#include <string>
class Rectangle {
private: // private メンバ (クラス内からのみアクセス可能) - デフォルト
double width_;
double height_;
public: // public メンバ (どこからでもアクセス可能)
// コンストラクタ
Rectangle(double w, double h) : width_(w), height_(h) {
std::cout << "Rectangle constructor called." << std::endl;
if (width_ < 0) width_ = 0; // 値の検証など
if (height_ < 0) height_ = 0;
}
// デストラクタ (オブジェクト破棄時に呼ばれる)
~Rectangle() {
std::cout << "Rectangle destructor called." << std::endl;
}
// アクセサ (ゲッター)
double getWidth() const { // const メンバ関数: メンバ変数を変更しないことを保証
return width_;
}
double getHeight() const {
return height_;
}
// ミューテータ (セッター)
void setWidth(double w) {
if (w >= 0) width_ = w;
}
void setHeight(double h) {
if (h >= 0) height_ = h;
}
// 他のメンバ関数
double area() const {
return width_ * height_;
}
void print() const {
std::cout << "Width: " << width_ << ", Height: " << height_ << std::endl;
}
}; // セミコロンが必要
int main() {
Rectangle rect1(10.0, 5.0);
// rect1.width_ = 15.0; // エラー: width_ は private
// public メンバ関数経由でアクセス・操作
rect1.print(); // Width: 10, Height: 5
std::cout << "Area: " << rect1.area() << std::endl; // Area: 50
rect1.setWidth(12.0);
std::cout << "New width: " << rect1.getWidth() << std::endl; // New width: 12
// スコープを抜ける際にデストラクタが自動的に呼ばれる
return 0;
}
アクセス指定子
| 指定子 | アクセス可能な範囲 | デフォルト (struct / class) |
|---|---|---|
public |
クラス内外、派生クラスからアクセス可能 | struct のデフォルト |
private |
そのクラスのメンバ関数、およびフレンド関数/クラスからのみアクセス可能 | class のデフォルト |
protected |
そのクラスのメンバ関数、フレンド関数/クラス、および派生クラスのメンバ関数からアクセス可能 | – |
コンストラクタとデストラクタ
- コンストラクタ: オブジェクトが生成されるときに自動的に呼び出される特殊なメンバ関数。クラス名と同じ名前で、戻り値の型はありません。オブジェクトの初期化を行います。オーバーロード可能です。
- デストラクタ: オブジェクトが破棄されるとき(スコープを抜ける、
deleteされるなど)に自動的に呼び出される特殊なメンバ関数。~+ クラス名という名前で、引数も戻り値もありません。リソースの解放処理などを行います。 - デフォルトコンストラクタ: 引数を取らないコンストラクタ。ユーザーがコンストラクタを一つも定義しない場合、コンパイラが暗黙的に生成します。
- コピーコンストラクタ: 同じ型の別のオブジェクトを引数に取り、そのオブジェクトのコピーとして新しいオブジェクトを生成するコンストラクタ。
ClassName(const ClassName& other)の形式。 - コピー代入演算子: 既存のオブジェクトに別のオブジェクトの内容をコピーする演算子。
ClassName& operator=(const ClassName& other)の形式。 - ムーブコンストラクタ (C++11): 他のオブジェクトのリソース(メモリなど)を効率的に「移動」させるコンストラクタ。
ClassName(ClassName&& other) noexceptの形式。コピーよりも高速な場合があります。 - ムーブ代入演算子 (C++11): 既存のオブジェクトに他のオブジェクトのリソースを「移動」させる演算子。
ClassName& operator=(ClassName&& other) noexceptの形式。 - Rule of Three/Five/Zero: コピーコンストラクタ、コピー代入演算子、デストラクタのうちいずれか一つをユーザー定義した場合、他の二つも定義する必要があるという原則(Rule of Three)。C++11以降は、ムーブコンストラクタとムーブ代入演算子も考慮し、Rule of Fiveとなります。ただし、可能であればこれらの特殊メンバ関数を定義せず、コンパイラ生成のものに任せる(Rule of Zero)のが、リソース管理(スマートポインタなど)をクラスのメンバに委譲するモダンなアプローチです。
#include <iostream>
#include <utility> // std::move
class MyString {
private:
char* data_ = nullptr;
size_t size_ = 0;
void log(const char* msg) const {
std::cout << "[" << this << "] " << msg << " (size: " << size_ << ")" << std::endl;
}
public:
// デフォルトコンストラクタ
MyString() { log("Default Constructor"); }
// 引数付きコンストラクタ
MyString(const char* s) {
log("Constructor from C-string");
if (s) {
size_ = strlen(s);
data_ = new char[size_ + 1];
strcpy(data_, s);
}
}
// デストラクタ
~MyString() {
log("Destructor");
delete[] data_; // 動的確保したメモリを解放
data_ = nullptr; // 安全のため
size_ = 0;
}
// コピーコンストラクタ (Rule of Five/Three)
MyString(const MyString& other) : size_(other.size_) {
log("Copy Constructor");
if (other.data_) {
data_ = new char[size_ + 1];
strcpy(data_, other.data_);
} else {
data_ = nullptr;
}
}
// コピー代入演算子 (Rule of Five/Three) - コピー&スワップイディオム
MyString& operator=(const MyString& other) {
log("Copy Assignment Operator");
if (this != &other) { // 自己代入チェック
MyString temp(other); // コピーコンストラクタで一時オブジェクト作成
swap(*this, temp); // 中身を交換 (下の swap メンバ関数)
} // temp はここで破棄され、古いリソースが解放される
return *this;
}
// ムーブコンストラクタ (C++11, Rule of Five)
MyString(MyString&& other) noexcept // noexcept が重要
: data_(other.data_), size_(other.size_)
{
log("Move Constructor");
// ムーブ元のリソースを無効化 (デストラクタで二重解放しないように)
other.data_ = nullptr;
other.size_ = 0;
}
// ムーブ代入演算子 (C++11, Rule of Five)
MyString& operator=(MyString&& other) noexcept {
log("Move Assignment Operator");
if (this != &other) {
// 既存のリソースを解放 (自己ムーブ代入に備える必要は薄いが念のため)
delete[] data_;
// リソースをムーブ
data_ = other.data_;
size_ = other.size_;
// ムーブ元を無効化
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
// メンバ関数 swap (コピー&スワップイディオムで使用)
friend void swap(MyString& first, MyString& second) noexcept {
using std::swap;
swap(first.size_, second.size_);
swap(first.data_, second.data_);
first.log("Swapped (first)");
second.log("Swapped (second)");
}
void print() const {
if (data_) std::cout << data_;
else std::cout << "(null)";
std::cout << std::endl;
}
size_t length() const { return size_; }
};
MyString createMyString() {
return MyString("Temporary"); // RVO/NRVO が効く場合、ムーブもコピーも省略されることがある
}
int main() {
MyString s1("Hello"); // Constructor from C-string
MyString s2 = s1; // Copy Constructor
MyString s3; // Default Constructor
s3 = s1; // Copy Assignment Operator
MyString s4 = createMyString(); // Constructor from C-string, Move Constructor (or RVO)
MyString s5("World"); // Constructor from C-string
MyString s6 = std::move(s5); // Move Constructor (s5 は空になる)
s1.print(); // Hello
s2.print(); // Hello
s3.print(); // Hello
s4.print(); // Temporary
s5.print(); // (null)
s6.print(); // World
std::cout << "End of main" << std::endl;
// デストラクタが s6, s4, s3, s2, s1 の順 (生成と逆順) で呼ばれる
return 0;
}
継承 (Inheritance)
既存のクラス(基底クラス、親クラス)の特性を引き継いで新しいクラス(派生クラス、子クラス)を作成する仕組み。コードの再利用性や拡張性を高めます。
#include <iostream>
#include <string>
// 基底クラス (Base Class / Parent Class)
class Animal {
protected: // 派生クラスからはアクセス可能、外部からは不可
std::string name_;
public:
Animal(const std::string& name) : name_(name) {
std::cout << "Animal Constructor: " << name_ << std::endl;
}
// virtual デストラクタ: 派生クラスのデストラクタを正しく呼び出すために必須
virtual ~Animal() {
std::cout << "Animal Destructor: " << name_ << std::endl;
}
// virtual 関数: 派生クラスでオーバーライド(再定義)される可能性がある関数
virtual void speak() const {
std::cout << name_ << " makes a sound." << std::endl;
}
void move() const { // 非 virtual 関数 (オーバーライド非推奨)
std::cout << name_ << " moves." << std::endl;
}
};
// 派生クラス (Derived Class / Child Class) - public継承
class Dog : public Animal {
private:
std::string breed_;
public:
// 派生クラスのコンストラクタ: 基底クラスのコンストラクタを呼び出す
Dog(const std::string& name, const std::string& breed)
: Animal(name), breed_(breed) // 基底クラスのコンストラクタ呼び出し
{
std::cout << "Dog Constructor: " << name_ << " (" << breed_ << ")" << std::endl;
}
~Dog() override { // override キーワード (C++11): 基底クラスの仮想関数をオーバーライドしていることを明示
std::cout << "Dog Destructor: " << name_ << std::endl;
}
// 基底クラスの virtual 関数をオーバーライド
void speak() const override {
std::cout << name_ << " barks! Woof!" << std::endl;
}
void fetch() const {
std::cout << name_ << " fetches the ball." << std::endl;
}
};
// protected 継承: public/protectedメンバは protected になる
// private 継承: public/protectedメンバは private になる (あまり使われない)
// 多重継承: 複数の基底クラスを持つ (複雑になりやすいので注意 )
class Pet {
public:
virtual void play() const = 0; // 純粋仮想関数: 実装を持たない (これを持つクラスは抽象クラス)
virtual ~Pet() = default; // デストラクタも仮想に
};
class PlayfulDog : public Dog, public Pet {
public:
PlayfulDog(const std::string& name, const std::string& breed)
: Dog(name, breed) {}
~PlayfulDog() override { std::cout << "PlayfulDog Destructor" << std::endl; }
// Pet の純粋仮想関数を実装
void play() const override {
std::cout << name_ << " plays happily!" << std::endl;
}
};
int main() {
// ポリモーフィズム (Polymorphism): 基底クラスのポインタや参照で派生クラスのオブジェクトを扱える
std::unique_ptr<Animal> animalPtr = std::make_unique<Dog>("Buddy", "Golden Retriever");
animalPtr->speak(); // Dog::speak() が呼ばれる (仮想関数のおかげ)
animalPtr->move(); // Animal::move() が呼ばれる
// animalPtr->fetch(); // エラー: Animal には fetch() はない
// ダウンキャスト (派生クラスのメンバにアクセスしたい場合) - dynamic_cast (安全)
Dog* dogPtr = dynamic_cast<Dog*>(animalPtr.get());
if (dogPtr) {
dogPtr->fetch();
}
std::cout << "----" << std::endl;
PlayfulDog pDog("Max", "Labrador");
pDog.speak();
pDog.fetch();
pDog.play();
std::cout << "----" << std::endl;
// 基底クラスのポインタで扱う (PlayfulDogはAnimalでもあるしPetでもある)
Animal* baseAnimal = &pDog
Pet* basePet = &pDog
baseAnimal->speak();
basePet->play();
std::cout << "Exiting main..." << std::endl;
// animalPtr が破棄される際、仮想デストラクタにより Dog のデストラクタも呼ばれる
return 0;
}
virtual関数: 派生クラスで再定義(オーバーライド)される可能性のあるメンバ関数。基底クラスのポインタや参照を通じて派生クラスのオブジェクトを操作する際(ポリモーフィズム)、実行時のオブジェクトの型に応じた関数が呼び出される(動的ディスパッチ)。override(C++11): 派生クラスでメンバ関数を定義する際に付与し、その関数が基底クラスの仮想関数をオーバーライドしていることを明示する。意図しないオーバーライド(スペルミスなど)を防ぐ。final(C++11): クラス名の後に付けるとそのクラスが継承されるのを禁止する。仮想関数の後に付けるとその関数が派生クラスでオーバーライドされるのを禁止する。- 純粋仮想関数 (
= 0): 実装を持たない仮想関数。virtual void func() const = 0;のように宣言する。純粋仮想関数を一つでも持つクラスは抽象クラスとなり、インスタンス化できない。派生クラスで必ず実装(オーバーライド)する必要がある。インターフェース定義によく使われる。 - 仮想デストラクタ: 基底クラスのデストラクタは、派生クラスを持つ可能性がある場合、
virtualにすべき。これにより、基底クラスのポインタを通じて派生クラスのオブジェクトをdeleteしたときに、派生クラスのデストラクタが正しく呼び出される。
その他
thisポインタ: 非静的メンバ関数内で、その関数を呼び出したオブジェクト自身を指すポインタ。this->member_variableのように使う(通常は暗黙的に使われる)。- 静的メンバ (
static): 特定のオブジェクトではなく、クラス自体に関連付けられるメンバ変数やメンバ関数。- 静的メンバ変数: クラスの全オブジェクトで共有される変数。クラス定義の外で実体を定義する必要がある。
- 静的メンバ関数: 特定のオブジェクトを必要とせず、
ClassName::static_function()のように呼び出せる関数。thisポインタを持たず、非静的メンバには直接アクセスできない。
- フレンド (
friend): クラスが特定の外部関数や他のクラスに対して、自身のprivateおよびprotectedメンバへのアクセスを許可する仕組み。カプセル化を壊す可能性があるため、使用は慎重に。- フレンド関数:
friend ReturnType functionName(Arguments); - フレンドクラス:
friend class OtherClassName;
- フレンド関数:
#include <iostream>
class Counter {
private:
int value_ = 0;
// 静的メンバ変数 (クラスで共有)
static int total_counts_; // 宣言
// フレンドクラス宣言
friend class CounterFriend;
public:
Counter(int initial_value = 0) : value_(initial_value) {
total_counts_ += value_; // オブジェクト生成時に total_counts_ を更新
std::cout << "Counter created. Value: " << value_
<< ", Total: " << total_counts_ << std::endl;
}
~Counter() {
total_counts_ -= value_; // オブジェクト破棄時に total_counts_ を減算
std::cout << "Counter destroyed. Value: " << value_
<< ", Remaining Total: " << total_counts_ << std::endl;
}
void increment() {
this->value_++; // thisポインタの使用 (通常は value_++ で十分)
total_counts_++;
}
int getValue() const { return value_; }
// 静的メンバ関数
static int getTotalCounts() {
// return value_; // エラー: 静的メンバ関数からは非静的メンバにアクセスできない
return total_counts_;
}
// フレンド関数宣言 (外部関数 resetCounter をフレンドにする)
friend void resetCounter(Counter& c, int val);
};
// 静的メンバ変数の実体定義と初期化 (必須)
int Counter::total_counts_ = 0;
// フレンド関数の定義 (Counterのprivateメンバにアクセス可能)
void resetCounter(Counter& c, int val) {
total_counts_ -= c.value_; // privateメンバ total_counts_ に直接アクセス (これは良くない例かも)
// 通常は c.total_counts_ のようには書けない
// ※ 正しくは静的メンバなので Counter::total_counts_ を使うべき
Counter::total_counts_ -= c.value_; // 正しいアクセス
c.value_ = val; // privateメンバ value_ にアクセス可能
Counter::total_counts_ += c.value_;
std::cout << "Counter reset by friend function. New Value: " << c.value_
<< ", New Total: " << Counter::total_counts_ << std::endl;
}
// フレンドクラスの定義
class CounterFriend {
public:
void peek(const Counter& c) {
// Counter の private メンバにアクセス可能
std::cout << "Friend class peeking. Value: " << c.value_ << std::endl;
// std::cout << "Total via friend: " << c.total_counts_ << std::endl; // これはできない(静的メンバなので)
std::cout << "Total via friend (static access): " << Counter::total_counts_ << std::endl;
}
};
int main() {
std::cout << "Initial total: " << Counter::getTotalCounts() << std::endl; // 0
Counter c1(10); // Counter created. Value: 10, Total: 10
Counter c2(5); // Counter created. Value: 5, Total: 15
std::cout << "Current total: " << Counter::getTotalCounts() << std::endl; // 15
c1.increment(); // value_: 11, total_counts_: 16
c2.increment(); // value_: 6, total_counts_: 17
std::cout << "Total after increments: " << Counter::getTotalCounts() << std::endl; // 17
resetCounter(c1, 0); // フレンド関数呼び出し
// Counter reset by friend function. New Value: 0, New Total: 6
CounterFriend cf;
cf.peek(c2); // Friend class peeking. Value: 6
// Total via friend (static access): 6
std::cout << "End of main" << std::endl;
// c2, c1 の順でデストラクタが呼ばれる
// Counter destroyed. Value: 6, Remaining Total: 0
// Counter destroyed. Value: 0, Remaining Total: 0
return 0;
}
テンプレート (Templates)
型をパラメータ化する機能。同じロジックを異なるデータ型に対して再利用できます。ジェネリックプログラミングの核となります。
関数テンプレート
型を引数とする関数を定義します。コンパイラが関数呼び出し時の引数の型に基づいて、具体的な関数(テンプレートインスタンス)を生成します。
#include <iostream>
#include <string>
#include <vector>
// 関数テンプレートの定義
// typename または class キーワードを使用可能
template <typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// 複数の型パラメータを持つテンプレート
template <typename T1, typename T2>
void printPair(T1 first, T2 second) {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
// 非型テンプレートパラメータ (整数、ポインタ、参照など)
template <typename T, int Size>
void printArray(const T (&arr)[Size]) { // 配列への参照を受け取る
std::cout << "Array (size " << Size << "): ";
for (int i = 0; i < Size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}
// C++20: autoによる関数テンプレート (簡略記法)
auto add_auto(auto a, auto b) {
return a + b;
}
int main() {
// 関数テンプレートの呼び出し (型推論)
std::cout << "Max(5, 10): " << maximum(5, 10) << std::endl; // T = int
std::cout << "Max(3.14, 2.71): " << maximum(3.14, 2.71) << std::endl; // T = double
std::cout << "Max('a', 'z'): " << maximum('a', 'z') << std::endl; // T = char
// 型を明示的に指定することも可能
std::cout << "Max<double>(5, 10.5): " << maximum<double>(5, 10.5) << std::endl; // T = double
std::string s1 = "hello", s2 = "world";
std::cout << "Max(\"hello\", \"world\"): " << maximum(s1, s2) << std::endl; // T = std::string
printPair(10, "ten"); // T1 = int, T2 = const char*
printPair(3.14, true); // T1 = double, T2 = bool
int numbers[] = {1, 2, 3, 4, 5};
double values[] = {1.1, 2.2, 3.3};
printArray(numbers); // T = int, Size = 5 (推論される)
printArray(values); // T = double, Size = 3
// C++20 auto
std::cout << "add_auto(1, 2): " << add_auto(1, 2) << std::endl;
std::cout << "add_auto(1.5, 2.5): " << add_auto(1.5, 2.5) << std::endl;
return 0;
}
クラステンプレート
型をパラメータとするクラスを定義します。std::vector<T>, std::map<K, V> など、STLコンテナの多くはクラステンプレートです。
#include <iostream>
#include <vector>
#include <stdexcept> // std::out_of_range
// クラステンプレートの定義
template <class T> // class でも typename でも OK
class Stack {
private:
std::vector<T> elements_;
public:
bool isEmpty() const {
return elements_.empty();
}
void push(const T& item) {
elements_.push_back(item);
}
void push(T&& item) { // ムーブセマンティクス対応 (C++11)
elements_.push_back(std::move(item));
}
T pop() {
if (isEmpty()) {
throw std::out_of_range("Stack<>::pop(): empty stack");
}
T topItem = std::move(elements_.back()); // ムーブで取り出す
elements_.pop_back();
return topItem; // RVO期待
}
const T& top() const {
if (isEmpty()) {
throw std::out_of_range("Stack<>::top(): empty stack");
}
return elements_.back();
}
};
// テンプレートクラスのメンバ関数をクラス外で定義する場合
template <typename T>
class MyPair {
public:
T first, second;
MyPair(T f, T s); // コンストラクタ宣言
void print(); // メンバ関数宣言
};
// メンバ関数の定義 (テンプレートパラメータを再度指定)
template <typename T>
MyPair<T>::MyPair(T f, T s) : first(f), second(s) {}
template <typename T>
void MyPair<T>::print() {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
int main() {
// クラステンプレートのインスタンス化
Stack<int> intStack;
intStack.push(1);
intStack.push(2);
std::cout << "Top int: " << intStack.top() << std::endl; // 2
int poppedInt = intStack.pop();
std::cout << "Popped int: " << poppedInt << std::endl; // 2
Stack<std::string> stringStack;
stringStack.push("hello");
stringStack.push("world");
std::string topStr = stringStack.top(); // コピーされる
std::cout << "Top string: " << topStr << std::endl; // world
std::string poppedStr = stringStack.pop(); // ムーブされる
std::cout << "Popped string: " << poppedStr << std::endl; // world
// クラステンプレートのテンプレート引数推論 (CTAD - C++17)
MyPair p1(10, 20); // MyPair<int> に推論される
MyPair p2(1.2, 3.4); // MyPair<double> に推論される
// Stack s1 = {1, 2, 3}; // Stack は集成体ではないのでこれは不可
// Stack s2(intStack); // コピーコンストラクタがあれば推論可能
p1.print(); // (10, 20)
p2.print(); // (1.2, 3.4)
return 0;
}
テンプレートの特殊化 (Template Specialization)
特定の型に対して、テンプレートとは異なる特別な実装を提供します。
#include <iostream>
#include <cstring> // strcmp
// プライマリテンプレート
template <typename T>
class Comparer {
public:
static bool areEqual(const T& a, const T& b) {
std::cout << "Using generic comparer... ";
return a == b;
}
};
// テンプレートの完全特殊化 (特定の型 `const char*` に対する実装)
template <> // 型パラメータリストは空
class Comparer<const char*> {
public:
static bool areEqual(const char* a, const char* b) {
std::cout << "Using specialized comparer for const char*... ";
return (a != nullptr && b != nullptr && strcmp(a, b) == 0);
}
};
// 関数テンプレートの特殊化も可能 (ただしオーバーロードの方が推奨されることが多い)
template <typename T>
void printType(T value) {
std::cout << "Generic type" << std::endl;
}
template <>
void printType<int>(int value) {
std::cout << "Specialized for int: " << value << std::endl;
}
template <>
void printType<double>(double value) {
std::cout << "Specialized for double: " << value << std::endl;
}
int main() {
int i1 = 5, i2 = 5;
double d1 = 3.14, d2 = 3.14;
const char* str1 = "hello";
const char* str2 = "hello";
const char* str3 = "world";
std::cout << Comparer<int>::areEqual(i1, i2) << std::endl;
// Output: Using generic comparer... 1 (true)
std::cout << Comparer<double>::areEqual(d1, d2) << std::endl;
// Output: Using generic comparer... 1 (true)
// 特殊化されたバージョンが呼ばれる
std::cout << Comparer<const char*>::areEqual(str1, str2) << std::endl;
// Output: Using specialized comparer for const char*... 1 (true)
std::cout << Comparer<const char*>::areEqual(str1, str3) << std::endl;
// Output: Using specialized comparer for const char*... 0 (false)
printType(10); // Specialized for int: 10
printType(3.5); // Specialized for double: 3.5
printType("abc"); // Generic type
return 0;
}
可変長引数テンプレート (Variadic Templates) (C++11)
任意の数・型の引数を取ることができるテンプレートです。printf や std::tuple, std::function などで使われています。
#include <iostream>
#include <string>
#include <tuple>
// ベースケース (再帰の停止条件)
void printAll() {
std::cout << std::endl; // 引数がなくなったら改行
}
// 再帰的な関数テンプレート
template <typename T, typename... Args> // typename... でパラメータパックを宣言
void printAll(const T& firstArg, const Args&... args) { // Args... でパラメータパックを展開
std::cout << firstArg << " ";
printAll(args...); // パラメータパックを再帰呼び出しに渡す
}
// sizeof... 演算子: パラメータパックの要素数を取得
template <typename... Args>
size_t countArgs(const Args&... args) {
return sizeof...(args);
// C++17 fold expression: return (args + ...); // 例: 加算の場合
}
// C++17 畳み込み式 (Fold Expressions) を使った printAll
template<typename... Args>
void printAll_fold(const Args&... args) {
// (... op pack) : (arg1 op (arg2 op (arg3 op ...))) - 右畳み込み
// (pack op ...) : (... op (argN-2 op (argN-1 op argN))) - 左畳み込み
// (init op ... op pack)
// (pack op ... op init)
// 左畳み込みでスペース区切り出力
((std::cout << args << " "), ...); // (arg1 << " "), (arg2 << " "), ...
std::cout << std::endl;
// 右畳み込みでスペース区切り出力
// (std::cout << ... << (args << " ")); // ちょっと書き方が違う
}
// タプルヘルパー (例)
template<std::size_t I = 0, typename... Tp>
typename std::enable_if<I == sizeof...(Tp), void>::type
print_tuple(const std::tuple<Tp...>& t) {
std::cout << ")" << std::endl; // 終了
}
template<std::size_t I = 0, typename... Tp>
typename std::enable_if<I < sizeof...(Tp), void>::type
print_tuple(const std::tuple<tp>& t) {
if constexpr (I == 0) std::cout << "("; // C++17 constexpr if
std::cout << std::get<i>(t);
if constexpr (I + 1 != sizeof...(Tp)) std::cout << ", ";
print_tuple<i>(t); // 再帰
}
int main() {
printAll(1, "hello", 3.14, true); // 1 hello 3.14 1
printAll(); // (改行のみ)
std::cout << "Count: " << countArgs(1, 2, 3) << std::endl; // Count: 3
std::cout << "Count: " << countArgs("a") << std::endl; // Count: 1
std::cout << "Count: " << countArgs() << std::endl; // Count: 0
std::cout << "Using fold expression: ";
printAll_fold(10, "world", 2.71); // 10 world 2.71
std::tuple<int double=""> myTuple(123, "Tuple Example", 9.87);
print_tuple(myTuple); // (123, Tuple Example, 9.87)
return 0;
}
</int></i></i></tp>
STL (Standard Template Library)
C++標準ライブラリの中核をなす、汎用的なコンテナ、アルゴリズム、イテレータなどの集まり。
主要なコンテナ
データを格納するためのクラス(クラステンプレート)。
| 種類 | コンテナ名 | ヘッダ | 特徴 |
|---|---|---|---|
| シーケンスコンテナ (順序付き) |
std::vector<T> |
<vector> |
動的配列。要素は連続メモリに配置。末尾への追加/削除が高速(O(1)償却)。中間への挿入/削除は遅い(O(N))。ランダムアクセス(O(1))可能。最もよく使われる。 |
std::deque<T> |
<deque> |
Double-Ended Queue。先頭・末尾への追加/削除が高速(O(1)償却)。要素は連続ではないが、ランダムアクセス(O(1)だがvectorよりは遅い)可能。 | |
std::list<T> |
<list> |
双方向リスト。任意の位置への挿入/削除が高速(O(1))。ランダムアクセス不可(O(N))。メモリオーバーヘッドが大きい。 | |
std::forward_list<T> |
<forward_list> |
単方向リスト (C++11)。listよりメモリ効率が良いが、逆方向走査不可。前方への挿入が高速(O(1))。 |
|
std::array<T, N> |
<array> |
固定長配列 (C++11)。C スタイル配列の代替。サイズはコンパイル時決定。スタック上に確保可能。vectorより若干高速な場合がある。 |
|
| 連想コンテナ (順序付き、キーでソート) |
std::map<K, V> |
<map> |
キー(K)と値(V)のペアを格納。キーはユニーク。キーで自動的にソート(通常は赤黒木)。検索/挿入/削除は対数時間(O(log N))。 |
std::set<K> |
<set> |
ユニークなキー(K)のみを格納。キーで自動的にソート。検索/挿入/削除は対数時間(O(log N))。要素の存在確認などに使用。 | |
std::multimap<K, V> |
<map> |
map と似ているが、重複するキーを許可する。 |
|
std::multiset<K> |
<set> |
set と似ているが、重複するキーを許可する。 |
|
| 非順序連想コンテナ (順序なし、ハッシュベース) (C++11) |
std::unordered_map<K, V> |
<unordered_map> |
ハッシュテーブルに基づくキーと値のペア。キーはユニーク。順序は保証されない。平均的な検索/挿入/削除は定数時間(O(1))だが、最悪時は線形時間(O(N))。キーにはハッシュ関数と等価比較が必要。 |
std::unordered_set<K> |
<unordered_set> |
ハッシュテーブルに基づくユニークなキーの集合。順序は保証されない。平均的な検索/挿入/削除は定数時間(O(1))。 | |
std::unordered_multimap<K, V> |
<unordered_map> |
unordered_map と似ているが、重複するキーを許可する。 |
|
std::unordered_multiset<K> |
<unordered_set> |
unordered_set と似ているが、重複するキーを許可する。 |
|
| コンテナアダプタ (既存コンテナをラップ) |
std::stack<T, C> |
<stack> |
LIFO (Last-In, First-Out) スタック。デフォルトは deque を内部コンテナ(C)として使用。push(), pop(), top() 操作。 |
std::queue<T, C> |
<queue> |
FIFO (First-In, First-Out) キュー。デフォルトは deque を内部コンテナとして使用。push() (末尾), pop() (先頭), front(), back() 操作。 |
|
std::priority_queue<T, C, Comp> |
<queue> |
優先度付きキュー。常に最も優先度が高い(デフォルトでは最大値)要素が先頭(top())に来る。デフォルトは vector と std::less (最大ヒープ) を使用。push(), pop(), top() 操作。 |
|
| その他 | std::string |
<string> |
文字列を扱うシーケンスコンテナ。 |
| その他 | std::span<T> |
<span> |
連続した要素シーケンスへの非所有参照 (C++20)。生配列、std::vector, std::array などを統一的に扱える。ビュー。 lightweight。 |
コンテナの共通操作 (一部):
c.empty(): コンテナが空かどうか (bool)c.size(): 要素数 (size_t)c.clear(): 全要素を削除c.begin(),c.end(): 先頭、末尾の次を指すイテレータc.cbegin(),c.cend(): const イテレータ (C++11)c.rbegin(),c.rend(): 逆順イテレータc.crbegin(),c.crend(): const 逆順イテレータ (C++11)swap(c1, c2)またはc1.swap(c2): コンテナの内容を交換
イテレータ (Iterators)
コンテナ内の要素を指し示し、順番にアクセスするためのオブジェクト。ポインタのようなインターフェースを持つことが多い。
- 入力イテレータ: 読み取り専用、一方向に一度だけ進める (例:
std::istream_iterator) - 出力イテレータ: 書き込み専用、一方向に進める (例:
std::ostream_iterator) - 前方イテレータ: 読み書き可能、一方向に複数回進める (例:
std::forward_list) - 双方向イテレータ: 読み書き可能、両方向に進める (例:
std::list,std::map,std::set) - ランダムアクセスイテレータ: 読み書き可能、両方向に進め、任意の位置にジャンプ可能 (
it + n,it - n,it[n]など) (例:std::vector,std::deque,std::array,std::string) - 連続イテレータ (C++20): ランダムアクセスイテレータであり、要素がメモリ上で連続していることを保証 (例:
std::vector,std::array,std::string,std::span)
#include <vector>
#include <iostream>
#include <list>
int main() {
std::vector<int> vec = {10, 20, 30, 40, 50};
// イテレータの取得
std::vector<int>::iterator it = vec.begin(); // 先頭要素を指す
std::vector<int>::iterator end = vec.end(); // 末尾要素の次を指す
// イテレータを使ったループ
std::cout << "Vector elements: ";
for (; it != end; ++it) { // イテレータを進める (++it)
std::cout << *it << " "; // イテレータが指す要素にアクセス (*it)
// *it = *it * 2; // 要素の変更も可能
}
std::cout << std::endl;
// const イテレータ (要素を変更しない場合)
std::cout << "Vector elements (const): ";
for (std::vector<int>::const_iterator cit = vec.cbegin(); cit != vec.cend(); ++cit) {
std::cout << *cit << " ";
// *cit = 100; // エラー: const イテレータでは変更不可
}
std::cout << std::endl;
// 逆順イテレータ
std::cout << "Vector elements (reverse): ";
for (auto rit = vec.rbegin(); rit != vec.rend(); ++rit) { // auto で型推論も便利
std::cout << *rit << " "; // 50 40 30 20 10
}
std::cout << std::endl;
// list (双方向イテレータ)
std::list<std::string> lst = {"apple", "banana", "cherry"};
auto list_it = lst.begin();
++list_it; // "banana" を指す
--list_it; // "apple" を指す (双方向なので -- も可能)
// list_it = list_it + 2; // エラー: 双方向イテレータは算術演算不可
return 0;
}
アルゴリズム (Algorithms)
コンテナ内の要素に対して、ソート、検索、コピー、変換などの一般的な操作を行う関数テンプレート群。主に <algorithm> と <numeric> ヘッダに含まれる。イテレータ範囲に対して動作する。
#include <iostream>
#include <vector>
#include <list>
#include <algorithm> // 多くのアルゴリズム
#include <numeric> // accumulate など
#include <string>
// ラムダ式や関数オブジェクトを述語として渡すことが多い
bool is_odd(int n) { return n % 2 != 0; }
struct GreaterThan {
int threshold;
GreaterThan(int t) : threshold(t) {}
bool operator()(int n) const { // 関数呼び出し演算子をオーバーロード
return n > threshold;
}
};
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
std::list<int> l(v.size()); // vと同じサイズのlist
//--- 非変更シーケンス操作 ---
// for_each: 各要素に関数を適用
std::cout << "for_each: ";
std::for_each(v.cbegin(), v.cend(), [](int x){ std::cout << x << " "; });
std::cout << std::endl;
// find: 値を検索 (最初のイテレータを返す)
auto it_find = std::find(v.cbegin(), v.cend(), 5);
if (it_find != v.cend()) {
std::cout << "Found 5 at index: " << std::distance(v.cbegin(), it_find) << std::endl;
}
// find_if: 条件に合う最初の要素を検索
auto it_find_if = std::find_if(v.cbegin(), v.cend(), is_odd); // is_odd を述語として渡す
if (it_find_if != v.cend()) {
std::cout << "First odd number: " << *it_find_if << std::endl; // 3
}
// count: 値に一致する要素数をカウント
size_t count_1 = std::count(v.cbegin(), v.cend(), 1);
std::cout << "Count of 1: " << count_1 << std::endl; // 2
// count_if: 条件に合う要素数をカウント
size_t count_gt_4 = std::count_if(v.cbegin(), v.cend(), GreaterThan(4)); // 関数オブジェクト
std::cout << "Count > 4: " << count_gt_4 << std::endl; // 3 (5, 9, 6)
// equal: 2つの範囲が等しいか比較
std::vector<int> v2 = {3, 1, 4, 1, 5, 9, 2, 6};
bool are_equal = std::equal(v.cbegin(), v.cend(), v2.cbegin());
std::cout << "v and v2 are equal: " << std::boolalpha << are_equal << std::endl; // true
// mismatch: 最初に一致しない要素ペアのイテレータを返す
// search: ある範囲が別の範囲に含まれるか検索
//--- 変更シーケンス操作 ---
// copy: 範囲を別の範囲にコピー
std::copy(v.cbegin(), v.cend(), l.begin()); // v を l にコピー
std::cout << "Copied list l: ";
std::for_each(l.cbegin(), l.cend(), [](int x){ std::cout << x << " "; });
std::cout << std::endl;
// copy_if: 条件に合う要素のみコピー
// move: 範囲を別の範囲にムーブ (C++11)
// transform: 各要素に関数を適用し、結果を別の範囲に格納
std::vector<int> v_doubled(v.size());
std::transform(v.cbegin(), v.cend(), v_doubled.begin(), [](int x){ return x * 2; });
std::cout << "v_doubled: ";
std::for_each(v_doubled.cbegin(), v_doubled.cend(), [](int x){ std::cout << x << " "; });
std::cout << std::endl;
// replace: 特定の値を別の値に置換
std::replace(v.begin(), v.end(), 1, 100); // v 中の 1 を 100 に置換
std::cout << "v after replace: ";
std::for_each(v.cbegin(), v.cend(), [](int x){ std::cout << x << " "; }); // 3 100 4 100 5 9 2 6
std::cout << std::endl;
// replace_if: 条件に合う要素を別の値に置換
// fill: 範囲を指定した値で埋める
std::vector<int> v_zeros(5);
std::fill(v_zeros.begin(), v_zeros.end(), 0); // 全て 0 に
// remove: 特定の値を削除 (実際には末尾に移動し、末尾のイテレータを返す。erase と組み合わせる)
// erase-remove イディオム
auto new_end = std::remove(v.begin(), v.end(), 100); // 100 を論理的に削除
std::cout << "v after remove (logical): ";
std::for_each(v.cbegin(), new_end, [](int x){ std::cout << x << " "; }); // 3 4 5 9 2 6
std::cout << std::endl;
v.erase(new_end, v.end()); // 実際に末尾の要素を削除
std::cout << "v after erase: ";
std::for_each(v.cbegin(), v.cend(), [](int x){ std::cout << x << " "; }); // 3 4 5 9 2 6
std::cout << std::endl;
// remove_if: 条件に合う要素を削除 (erase-remove と同様)
// unique: 隣接する重複要素を削除 (erase-remove と同様。事前にソートが必要なことが多い)
std::vector<int> v_dup = {1, 2, 2, 3, 3, 3, 1, 1, 2};
std::sort(v_dup.begin(), v_dup.end()); // ソート: 1 1 1 2 2 2 3 3 3
auto unique_end = std::unique(v_dup.begin(), v_dup.end());
v_dup.erase(unique_end, v_dup.end());
std::cout << "v_dup after unique: ";
std::for_each(v_dup.cbegin(), v_dup.cend(), [](int x){ std::cout << x << " "; }); // 1 2 3
std::cout << std::endl;
// reverse: 範囲を逆順にする
std::reverse(v.begin(), v.end());
std::cout << "v reversed: ";
std::for_each(v.cbegin(), v.cend(), [](int x){ std::cout << x << " "; }); // 6 2 9 5 4 3
std::cout << std::endl;
//--- ソート操作 ---
// sort: 範囲をソート (デフォルトは昇順)
std::sort(v.begin(), v.end()); // 2 3 4 5 6 9
std::cout << "v sorted: ";
std::for_each(v.cbegin(), v.cend(), [](int x){ std::cout << x << " "; });
std::cout << std::endl;
// 降順ソート (比較関数を提供)
std::sort(v.begin(), v.end(), std::greater<int>()); // std::greater を使用
// またはラムダ式: std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
std::cout << "v sorted descending: ";
std::for_each(v.cbegin(), v.cend(), [](int x){ std::cout << x << " "; }); // 9 6 5 4 3 2
std::cout << std::endl;
// stable_sort: ソート後も等価な要素の相対順序を保持
// partial_sort: 範囲の一部だけソート
// is_sorted: 範囲がソート済みかチェック
//--- 二分探索操作 (ソート済み範囲に対して使用) ---
std::vector<int> sorted_v = {2, 3, 4, 5, 6, 9};
// binary_search: 値が存在するかチェック (高速 O(log N))
bool has_5 = std::binary_search(sorted_v.cbegin(), sorted_v.cend(), 5);
std::cout << "Binary search for 5: " << has_5 << std::endl; // true
// lower_bound: 値以上の最初の要素のイテレータを返す
auto lb = std::lower_bound(sorted_v.begin(), sorted_v.end(), 4);
std::cout << "Lower bound for 4: " << *lb << " at index " << std::distance(sorted_v.begin(), lb) << std::endl; // 4 at index 2
// upper_bound: 値より大きい最初の要素のイテレータを返す
auto ub = std::upper_bound(sorted_v.begin(), sorted_v.end(), 4);
std::cout << "Upper bound for 4: " << *ub << " at index " << std::distance(sorted_v.begin(), ub) << std::endl; // 5 at index 3
// equal_range: lower_bound と upper_bound のペアを返す
//--- 数値演算 (<numeric>) ---
// accumulate: 範囲の合計値などを計算
int sum = std::accumulate(sorted_v.cbegin(), sorted_v.cend(), 0); // 初期値 0
std::cout << "Sum of sorted_v: " << sum << std::endl; // 2+3+4+5+6+9 = 29
// 積を計算 (ラムダ式で二項演算を指定)
long long product = std::accumulate(sorted_v.cbegin(), sorted_v.cend(), 1LL,
std::multiplies<long long>());
// [](long long acc, int x){ return acc * x; });
std::cout << "Product of sorted_v: " << product << std::endl; // 2*3*4*5*6*9 = 6480
// inner_product: 内積 (要素ごとの積の和)
std::vector<int> w = {1, 2, 1, 2, 1, 2};
int inner_prod = std::inner_product(sorted_v.cbegin(), sorted_v.cend(), w.cbegin(), 0);
// (2*1)+(3*2)+(4*1)+(5*2)+(6*1)+(9*2) = 2+6+4+10+6+18 = 46
std::cout << "Inner product: " << inner_prod << std::endl;
// partial_sum: 部分和を計算して格納
std::vector<int> partial_sums(sorted_v.size());
std::partial_sum(sorted_v.cbegin(), sorted_v.cend(), partial_sums.begin());
std::cout << "Partial sums: ";
std::for_each(partial_sums.cbegin(), partial_sums.cend(), [](int x){ std::cout << x << " "; }); // 2 5 9 14 20 29
std::cout << std::endl;
// iota: 連番で範囲を埋める (C++11)
std::vector<int> sequence(10);
std::iota(sequence.begin(), sequence.end(), 1); // 1 から始まる連番
std::cout << "Sequence (iota): ";
std::for_each(sequence.cbegin(), sequence.cend(), [](int x){ std::cout << x << " "; }); // 1 2 3 4 5 6 7 8 9 10
std::cout << std::endl;
return 0;
}
入出力 (I/O Streams)
コンソールやファイルとのデータ送受信を行うための機能。
標準入出力 (<iostream>)
std::cin: 標準入力 (通常はキーボード) からデータを読み取るオブジェクト。std::cout: 標準出力 (通常はコンソール画面) にデータを出力するオブジェクト。std::cerr: 標準エラー出力 (バッファリングされない)。エラーメッセージ用。std::clog: 標準エラー出力 (バッファリングされる)。ログメッセージ用。- 抽出演算子 (
>>):cinからデータを読み込む。空白文字で区切られる。 - 挿入演算子 (
<<):cout,cerr,clogにデータを出力する。
#include <iostream>
#include <string>
#include <limits> // std::numeric_limits
int main() {
// 出力
std::cout << "Hello, C++ I/O!" << std::endl; // std::endl は改行してバッファをフラッシュ
int age = 30;
double pi = 3.14159;
std::cout << "Age: " << age << ", Pi: " << pi << "\n"; // '\n' は改行のみ (フラッシュしない)
// エラー出力
std::cerr << "[Error] Something went wrong!" << std::endl;
// 入力
std::string name;
int user_age;
std::cout << "Enter your name: ";
// std::cin >> name; // 空白文字まで読み込む
// 一行読み込む場合 (推奨)
std::getline(std::cin, name);
std::cout << "Enter your age: ";
std::cin >> user_age;
// 入力エラーチェック
if (!std::cin) { // または if (std::cin.fail())
std::cerr << "Invalid input for age." << std::endl;
// エラー状態をクリア
std::cin.clear();
// 不正な入力をバッファから捨てる
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return 1;
}
// cin で数値などを読み取った後、getline を使う場合は改行文字が残っている可能性があるので注意
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 改行文字を読み飛ばす
std::cout << "Hello, " << name << "! You are " << user_age << " years old." << std::endl;
return 0;
}
ファイル入出力 (<fstream>)
std::ofstream: ファイルへの出力 (書き込み) ストリーム。std::ifstream: ファイルからの入力 (読み込み) ストリーム。std::fstream: ファイルへの入出力両用ストリーム。
基本的な使い方は cin / cout と同様ですが、ファイルを開く操作が必要です。
#include <iostream>
#include <fstream> // ファイルI/O
#include <string>
#include <vector>
int main() {
const std::string filename = "mydata.txt";
//--- ファイルへの書き込み (ofstream) ---
// 1. オブジェクト作成時にファイル名を指定 (自動で開く)
std::ofstream outFile(filename); // デフォルトは std::ios::out | std::ios::trunc (上書き)
// std::ofstream outFile(filename, std::ios::app); // 追記モード
if (!outFile.is_open()) { // または if (!outFile)
std::cerr << "Error opening file for writing: " << filename << std::endl;
return 1;
}
// cout と同じように書き込める
outFile << "This is line 1." << std::endl;
outFile << "Writing some numbers: " << 123 << " " << 4.56 << std::endl;
std::vector<std::string> lines = {"Line A", "Line B"};
for(const auto& line : lines) {
outFile << line << std::endl;
}
outFile.close(); // デストラクタでも自動で閉じるが、明示的に閉じても良い
std::cout << "Successfully wrote to " << filename << std::endl;
//--- ファイルからの読み込み (ifstream) ---
std::ifstream inFile(filename); // デフォルトは std::ios::in
if (!inFile.is_open()) {
std::cerr << "Error opening file for reading: " << filename << std::endl;
return 1;
}
std::cout << "\nReading from " << filename << ":" << std::endl;
std::string line;
int line_num = 1;
// 1行ずつ読み込む (一般的な方法)
while (std::getline(inFile, line)) {
std::cout << "Line " << line_num++ << ": " << line << std::endl;
}
// ファイルポインタを先頭に戻す (再度読み込む場合など)
inFile.clear(); // EOFフラグなどをクリア
inFile.seekg(0, std::ios::beg); // ファイルの先頭に移動
// 単語ごとに読み込む (cin と同様)
std::cout << "\nReading words:" << std::endl;
std::string word;
while (inFile >> word) { // >> は空白で区切る
std::cout << "Word: " << word << std::endl;
}
inFile.close(); // 明示的に閉じる
//--- fstream (入出力両用) ---
// std::fstream ioFile(filename, std::ios::in | std::ios::out | std::ios::app);
// ... 読み書き操作 ... seekg/seekp で位置を調整する必要があることが多い
// ioFile.close();
return 0;
}
ファイルモードフラグ (std::ios 名前空間):
in: 入力用に開く (ifstream のデフォルト)out: 出力用に開く (ofstream のデフォルト)binary: バイナリモードで開く (テキストモードがデフォルト)ate: ファイルを開いた直後に末尾に移動するapp: 追記モード (出力は常に末尾に行われる)trunc: ファイルを開くとき、既存の内容を破棄する (out のデフォルト動作の一部)- 複数のモードは
|(ビットOR) で組み合わせる (例:std::ios::in | std::ios::binary)。
文字列ストリーム (<sstream>)
メモリ上の文字列をファイルのように扱えるストリーム。文字列のパースやフォーマット済み文字列の作成に便利。
std::ostringstream: 文字列への出力(書き込み)用。std::istringstream: 文字列からの入力(読み込み)用。std::stringstream: 文字列の入出力両用。
#include <iostream>
#include <sstream> // 文字列ストリーム
#include <string>
#include <vector>
#include <iomanip> // std::setprecision など
int main() {
//--- ostringstream: 様々な型のデータを文字列に変換 ---
std::ostringstream oss;
std::string name = "Alice";
int age = 25;
double score = 95.5;
// cout のように使える
oss << "User Info:" << std::endl;
oss << "Name: " << name << std::endl;
oss << "Age: " << age << std::endl;
oss << "Score: " << std::fixed << std::setprecision(2) << score; // フォーマット指定も可能
// 結果の文字列を取得
std::string result_string = oss.str();
std::cout << "--- ostringstream result ---" << std::endl;
std::cout << result_string << std::endl;
//--- istringstream: 文字列をパースしてデータを取り出す ---
std::string data = "Bob 30 88.7";
std::istringstream iss(data);
std::string parsed_name;
int parsed_age;
double parsed_score;
// cin のように使える
iss >> parsed_name >> parsed_age >> parsed_score;
// パース結果の確認
if (!iss.fail()) { // パースが成功したかチェック
std::cout << "\n--- istringstream result ---" << std::endl;
std::cout << "Parsed Name: " << parsed_name << std::endl; // Bob
std::cout << "Parsed Age: " << parsed_age << std::endl; // 30
std::cout << "Parsed Score: " << parsed_score << std::endl; // 88.7
} else {
std::cerr << "Failed to parse string: " << data << std::endl;
}
// カンマ区切りデータのパース例
std::string csv_line = "item1,100,true";
std::stringstream ss_csv(csv_line); // stringstream でも可
std::string segment;
std::vector<std::string> seglist;
while(std::getline(ss_csv, segment, ',')) { // 区切り文字を ',' に指定
seglist.push_back(segment);
}
std::cout << "\n--- CSV Parse result ---" << std::endl;
for(const auto& s : seglist) {
std::cout << s << std::endl; // item1, 100, true がそれぞれ出力される
}
return 0;
}
入出力マニピュレータ (<iomanip>)
出力のフォーマット(幅、精度、基数など)を制御するための関数。
| マニピュレータ | ヘッダ | 説明 | 例 |
|---|---|---|---|
std::endl | <iostream> | 改行し、バッファをフラッシュする。 | std::cout << "Hi" << std::endl; |
std::ends | <iostream> | ヌル文字 (‘\0’) を挿入し、バッファをフラッシュする。 | oss << "text" << std::ends; |
std::flush | <iostream> | バッファをフラッシュする (改行しない)。 | std::cout << "Processing..." << std::flush; |
std::setw(n) | <iomanip> | 次の出力フィールドの最小幅を n 文字に設定する (右寄せがデフォルト)。効果はその直後の出力にのみ適用される。 | std::cout << std::setw(10) << 123; |
std::setfill(c) | <iomanip> | setw で指定した幅に満たない場合に埋める文字 c を設定する (デフォルトは空白)。 | std::cout << std::setfill('*') << std::setw(10) << 123; |
std::left | <iostream> | 左寄せにする (setw と共に使用)。 | std::cout << std::left << std::setw(10) << 123; |
std::right | <iostream> | 右寄せにする (デフォルト)。 | std::cout << std::right << std::setw(10) << 123; |
std::internal | <iostream> | 符号や基数指示子を左端、数値を右端に配置する (setw と共に使用)。 | std::cout << std::internal << std::showpos << std::setw(10) << 123; |
std::setprecision(n) | <iomanip> | 浮動小数点数の出力精度 (表示する桁数) を n に設定する。動作は fixed/scientific フラグに依存。 | std::cout << std::setprecision(4) << M_PI; |
std::fixed | <iostream> | 浮動小数点数を固定小数点表記で出力する (setprecision は小数点以下の桁数を指定)。 | std::cout << std::fixed << std::setprecision(2) << 123.456; |
std::scientific | <iostream> | 浮動小数点数を科学技術表記 (指数表記) で出力する (setprecision は仮数部の桁数を指定)。 | std::cout << std::scientific << 12345.6; |
std::hex | <iostream> | 整数を16進数で出力する。 | std::cout << std::hex << 255; |
std::oct | <iostream> | 整数を8進数で出力する。 | std::cout << std::oct << 63; |
std::dec | <iostream> | 整数を10進数で出力する (デフォルト)。 | std::cout << std::dec << 0xFF; |
std::showbase | <iostream> | 基数を示す接頭辞 (0x, 0) を表示する。 | std::cout << std::showbase << std::hex << 255; |
std::noshowbase | <iostream> | 基数を示す接頭辞を表示しない (デフォルト)。 | |
std::showpos | <iostream> | 正の数に `+` 記号を表示する。 | std::cout << std::showpos << 100; |
std::noshowpos | <iostream> | 正の数に `+` 記号を表示しない (デフォルト)。 | |
std::uppercase | <iostream> | 16進数表記の文字 (A-F) や科学技術表記の ‘E’ を大文字で表示する。 | std::cout << std::uppercase << std::hex << 255; |
std::nouppercase | <iostream> | 小文字で表示する (デフォルト)。 | |
std::boolalpha | <iostream> | bool 値を “true”, “false” として出力する。 | std::cout << std::boolalpha << true; |
std::noboolalpha | <iostream> | bool 値を 1, 0 として出力する (デフォルト)。 | |
std::put_time(tm*, fmt) | <iomanip> | std::tm 構造体の時刻を指定されたフォーマット文字列 fmt で出力する (C++11)。 | auto t = std::time(nullptr); auto tm = *std::localtime(&t); std::cout << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); |
std::get_time(tm*, fmt) | <iomanip> | 入力ストリームから指定されたフォーマット文字列 fmt で時刻を読み取り、std::tm 構造体に格納する (C++11)。 | std::tm tm{}; std::cin >> std::get_time(&tm, "%Y-%m-%d"); |
std::quoted(str) | <iomanip> | 文字列 str を引用符で囲み、内部の特殊文字をエスケープして出力/入力する (C++14)。スペースを含む文字列の扱いに便利。 | std::cout << std::quoted("Hello World"); |
例外処理 (Exception Handling)
プログラム実行中に発生した予期せぬエラー状況(例外)に対処するための仕組み。
try, catch, throw
tryブロック: 例外が発生する可能性のあるコードを囲む。throw式: 例外が発生したことを示す。任意の型のオブジェクトをスローできる(通常は標準例外クラスかその派生クラス)。catchブロック: スローされた例外を捕捉し、処理する。型に一致する最初のcatchブロックが実行される。
#include <iostream>
#include <vector>
#include <stdexcept> // 標準例外クラス (std::out_of_range, std::runtime_error など)
double divide(double numerator, double denominator) {
if (denominator == 0.0) {
// エラー状況を示す例外をスロー
throw std::runtime_error("Division by zero error!");
}
return numerator / denominator;
}
void processVector(const std::vector<int>& vec, size_t index) {
// vector::at() は範囲外アクセス時に std::out_of_range をスローする
try {
int value = vec.at(index); // 例外が発生する可能性のあるコード
std::cout << "Value at index " << index << ": " << value << std::endl;
double result = divide(10.0, static_cast<double>(value));
std::cout << "Division result: " << result << std::endl;
} catch (const std::out_of_range& oor) {
// std::out_of_range 型の例外を捕捉
std::cerr << "[Caught out_of_range] Error: " << oor.what() << std::endl;
// what() でエラーメッセージを取得
} catch (const std::runtime_error& rte) {
// std::runtime_error 型の例外を捕捉
std::cerr << "[Caught runtime_error] Error: " << rte.what() << std::endl;
} catch (const std::exception& e) {
// その他の標準例外 (std::exception から派生) を捕捉
std::cerr << "[Caught std::exception] Error: " << e.what() << std::endl;
} catch (...) {
// 全ての型の例外を捕捉 (最後の手段として使う)
std::cerr << "[Caught unknown exception]" << std::endl;
// throw; // 捕捉した例外を再スローすることも可能
}
// try-catch ブロックの後もプログラムは続行できる (捕捉された場合)
std::cout << "Continuing after try-catch block..." << std::endl;
}
int main() {
std::vector<int> myVec = {10, 0, 20};
std::cout << "--- Processing index 0 ---" << std::endl;
processVector(myVec, 0); // 正常系 (Value: 10, Division: 1.0)
std::cout << "\n--- Processing index 1 ---" << std::endl;
processVector(myVec, 1); // divide で runtime_error がスローされる
std::cout << "\n--- Processing index 5 ---" << std::endl;
processVector(myVec, 5); // vec.at() で out_of_range がスローされる
return 0;
}
標準例外クラス (<stdexcept>)
std::exception クラスを基底クラスとする、よく使われる例外クラス群。
std::exception: 全ての標準例外の基底クラス。what()メンバ関数でエラーメッセージ(C文字列)を取得できる。- 論理エラー (Logic Errors): プログラムの内部的な矛盾など、通常は修正可能なバグを示す。
std::logic_error(基底)std::domain_error: 引数が有効範囲外 (例: sqrt(-1))std::invalid_argument: 不適切な引数std::length_error: 長すぎるオブジェクトを作成しようとしたstd::out_of_range: 範囲外アクセス (例:vector::at())
- 実行時エラー (Runtime Errors): プログラム外部の要因など、実行時にしか検出できないエラーを示す。
std::runtime_error(基底)std::overflow_error: 算術オーバーフローstd::underflow_error: 算術アンダーフローstd::range_error: 演算結果が表現可能な範囲を超えたstd::system_error(<system_error>): OS など低レベル API のエラー (std::error_codeを含む)
- その他
std::bad_alloc(<new>): メモリ確保 (new) の失敗。std::bad_cast(<typeinfo>):dynamic_castによる参照へのキャスト失敗。std::bad_typeid(<typeinfo>): ヌルポインタに対するtypeidの適用。
独自の例外クラスを定義する場合は、std::exception や適切な標準例外クラスから派生させることが推奨されます。
#include <stdexcept>
#include <string>
#include <iostream>
// 独自の例外クラス
class MyCustomError : public std::runtime_error {
public:
// 基底クラスのコンストラクタを呼び出す
MyCustomError(const std::string& message)
: std::runtime_error("MyCustomError: " + message) {}
};
void checkValue(int value) {
if (value < 0) {
throw MyCustomError("Value cannot be negative.");
}
std::cout << "Value is OK: " << value << std::endl;
}
int main() {
try {
checkValue(10);
checkValue(-5); // ここで例外がスローされる
checkValue(20); // これは実行されない
} catch (const MyCustomError& e) {
std::cerr << "Caught custom exception: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Caught standard exception: " << e.what() << std::endl;
}
return 0;
}
noexcept 指定子 (C++11)
関数が例外をスローしないことをコンパイラに伝えるための指定子。最適化に役立つことがあります。
noexceptまたはnoexcept(true): 関数は例外をスローしない。もしスローされた場合、std::terminateが呼び出されプログラムが異常終了する。noexcept(false): 関数は例外をスローする可能性がある (デフォルト)。noexcept(<条件式>): 条件式がtrueと評価される場合にnoexcept(true)と同等。テンプレートなどで、特定の型操作が例外を投げない場合に指定するのに使う。- ムーブコンストラクタやムーブ代入演算子は、可能であれば
noexceptにすることが強く推奨される。これにより、std::vectorのリサイズなどでコピーではなくムーブが選択されやすくなる。
#include <iostream>
#include <utility> // std::move
#include <vector>
void func_throws() { // noexcept(false) と同等
throw std::runtime_error("Exception from func_throws");
}
void func_no_throw() noexcept { // 例外をスローしないことを保証
std::cout << "func_no_throw executed." << std::endl;
// throw 1; // もしここで例外を投げると std::terminate が呼ばれる
}
template <typename T>
void swap_maybe_throws(T& a, T& b) noexcept(std::is_nothrow_move_constructible_v<T> &&
std::is_nothrow_move_assignable_v<T>)
{
T temp = std::move(a); // ムーブコンストラクタ
a = std::move(b); // ムーブ代入
b = std::move(temp); // ムーブ代入
std::cout << "Swapped using move (noexcept("
<< noexcept(swap_maybe_throws(a, b)) // noexcept演算子で確認
<< "))" << std::endl;
}
struct MayThrowOnMove {
std::vector<int> data;
MayThrowOnMove() = default;
// ムーブ操作が例外を投げる可能性がある (noexceptではない)
MayThrowOnMove(MayThrowOnMove&& other) : data(std::move(other.data)) { /* potentially throws */ }
MayThrowOnMove& operator=(MayThrowOnMove&& other) { data = std::move(other.data); /* potentially throws */ return *this;}
};
struct NoThrowOnMove {
int* data = nullptr;
NoThrowOnMove() = default;
// ムーブ操作が例外を投げないことを保証 (noexcept)
NoThrowOnMove(NoThrowOnMove&& other) noexcept : data(other.data) { other.data = nullptr; }
NoThrowOnMove& operator=(NoThrowOnMove&& other) noexcept {
if(this != &other){ delete data; data = other.data; other.data = nullptr;}
return *this;
}
~NoThrowOnMove() { delete data; }
};
int main() {
try {
func_no_throw();
// func_throws();
} catch (const std::exception& e) {
std::cerr << "Caught: " << e.what() << std::endl;
}
std::cout << std::boolalpha;
std::cout << "func_no_throw is noexcept? " << noexcept(func_no_throw()) << std::endl; // true
std::cout << "func_throws is noexcept? " << noexcept(func_throws()) << std::endl; // false
int x = 1, y = 2;
swap_maybe_throws(x, y); // Swapped using move (noexcept(true))
MayThrowOnMove mtm1, mtm2;
swap_maybe_throws(mtm1, mtm2); // Swapped using move (noexcept(false))
NoThrowOnMove ntm1, ntm2;
swap_maybe_throws(ntm1, ntm2); // Swapped using move (noexcept(true))
return 0;
}
</int>
その他の重要な機能
型キャスト、マルチスレッド、時間、モダンC++の機能など。
型キャスト (Type Casting)
あるデータ型を別のデータ型に変換します。C++ では、C スタイルのキャスト (new_type)expression よりも、目的が明確な4種類のキャスト演算子が推奨されます。
| キャスト演算子 | 説明 | 主な用途 |
|---|---|---|
static_cast<new_type>(expr) |
コンパイル時に型チェックが行われる、比較的安全なキャスト。明確な型変換(数値型間、ポインタ間のアップキャスト/ダウンキャスト(非ポリモーフィック)、void* との変換など)。 | 数値型間の変換、互換性のあるポインタ型間の変換、void* への/からの変換。継承関係でのアップキャスト(派生クラス→基底クラス)。基底クラス→派生クラスのダウンキャスト(安全性の保証なし)。 |
dynamic_cast<new_type>(expr) |
実行時に型チェックが行われるキャスト。ポリモーフィックなクラス階層(仮想関数を持つクラス)でのポインタまたは参照のダウンキャスト(基底クラス→派生クラス)に使用。失敗した場合、ポインタなら nullptr、参照なら std::bad_cast 例外を返す/スローする。RTTI (実行時型情報) が必要。 |
ポリモーフィックなクラス階層における安全なダウンキャスト。 |
const_cast<new_type>(expr) |
const や volatile 修飾子を追加または削除するためのキャスト。型の変更はできない。乱用は危険 。通常は const 正当性 (const correctness) に問題がある場合に限定的に使用される。 |
const オブジェクトへのポインタ/参照から const を除去して、非 const な関数を呼び出す場合(ただし、元のオブジェクトが非 const として定義されている場合に限る)。 |
reinterpret_cast<new_type>(expr) |
最も危険なキャスト。ビットパターンをそのまま解釈し直すような、低レベルな型変換を行う。型安全性は全く保証されない。プラットフォーム依存のコードになりやすい。使用は最小限に留めるべき 。 | ポインタと整数間の変換、関連のないポインタ型間の変換など、非常に低レベルな操作。 |
#include <iostream>
#include <string>
#include <vector>
#include <typeinfo> // bad_cast
class Base { public: virtual ~Base() = default; virtual void print() const { std::cout << "Base\n"; } };
class Derived : public Base { public: void print() const override { std::cout << "Derived\n"; } void derived_only() { std::cout << "Derived Only Method\n"; } };
class Another { public: void another_method() {} };
void processConstData(const int* data) {
// data[0] = 100; // エラー: const ポインタ経由では変更不可
// どうしても非constなAPIに渡す必要がある場合 (元のデータが非constの場合のみ安全)
int* non_const_data = const_cast<int*>(data);
non_const_data[0] = 100; // 危険!元のデータがconstなら未定義動作
std::cout << "Modified via const_cast: " << data[0] << std::endl;
}
int main() {
// static_cast
double d = 3.14;
int i = static_cast<int>(d); // double から int へ (小数点以下切り捨て)
std::cout << "static_cast<int>(3.14): " << i << std::endl; // 3
Base* b_ptr = new Derived();
Derived* d_ptr_static = static_cast<Derived*>(b_ptr); // ダウンキャスト (非ポリモーフィックでも可能だが安全でない場合も)
d_ptr_static->print(); // Derived
void* void_p = b_ptr;
Base* b_ptr_from_void = static_cast<Base*>(void_p);
b_ptr_from_void->print(); // Base (実際はDerivedだが型はBase)
// dynamic_cast (ポリモーフィックなクラス階層で)
Derived* d_ptr_dynamic = dynamic_cast<Derived*>(b_ptr); // 安全なダウンキャスト
if (d_ptr_dynamic) {
std::cout << "dynamic_cast successful: ";
d_ptr_dynamic->derived_only(); // 派生クラス固有メソッド呼び出し
} else {
std::cout << "dynamic_cast failed." << std::endl;
}
Base* b_ptr_base = new Base();
Derived* d_ptr_fail = dynamic_cast<Derived*>(b_ptr_base); // これは失敗するはず
if (!d_ptr_fail) {
std::cout << "dynamic_cast from Base* to Derived* failed (expected)." << std::endl;
}
delete b_ptr_base;
// dynamic_cast for references (失敗すると bad_cast 例外)
Derived d_obj;
Base& b_ref = d_obj;
try {
Derived& d_ref = dynamic_cast<Derived&>(b_ref);
std::cout << "dynamic_cast reference successful." << std::endl;
} catch (const std::bad_cast& e) {
std::cerr << "dynamic_cast reference failed: " << e.what() << std::endl;
}
// const_cast
const int const_val = 10;
const int* p_const_val = &const_val;
// int* p_val = const_cast<int*>(p_const_val);
// *p_val = 20; // 未定義動作! 元が const なオブジェクトを変更しようとしている
int non_const_arr[] = {1, 2, 3};
processConstData(non_const_arr); // 元が非constなので、この場合は安全
// reinterpret_cast (危険な例)
long addr = reinterpret_cast<long>(b_ptr); // ポインタを整数に変換
std::cout << "Pointer address as long: " << addr << std::endl;
Base* b_ptr_re = reinterpret_cast<Base*>(addr); // 整数をポインタに戻す
b_ptr_re->print(); // Derived (動くかもしれないが保証はない)
// 関連のない型へのキャスト (非常に危険)
// Another* another_ptr = reinterpret_cast<Another*>(b_ptr);
// another_ptr->another_method(); // 未定義動作!
delete b_ptr;
return 0;
}
マルチスレッド (<thread>, <mutex>, <atomic> etc.) (C++11)
プログラム内で複数の処理(スレッド)を並行して実行する機能。
std::thread(<thread>): スレッドを表すクラス。関数やラムダ式などを別スレッドで実行できる。thread t(function, args...): スレッド生成・開始。t.join(): スレッドの終了を待機する。t.detach(): スレッドを切り離し、待機せずに続行する(リソース管理に注意)。std::this_thread::sleep_for(duration): 現在のスレッドを指定時間スリープさせる。std::this_thread::get_id(): 現在のスレッドIDを取得。
std::mutex(<mutex>): ミューテックス (Mutual Exclusion)。共有リソースへのアクセスを排他制御するためのロック機構。一度に一つのスレッドしかロックできない。mutex m;m.lock(): ロックを取得(取得できない場合はブロック)。m.unlock(): ロックを解放。m.try_lock(): ロックを試み、取得できれば true、できなければ false を返す(ブロックしない)。
std::lock_guard<Mutex>(<mutex>): RAII ベースのミューテックスラッパー。スコープ開始時にロックを取得し、スコープ終了時に自動的に解放する。unlock()を忘れるミスを防げる。推奨されるロック方法。std::unique_lock<Mutex>(<mutex>):lock_guardより高機能なラッパー。ロックの遅延、手動でのロック/アンロック、所有権の移動、条件変数との連携などが可能。std::condition_variable(<condition_variable>): 条件変数。ある条件が満たされるまでスレッドを待機させる機構。std::unique_lockと共に使用する。cv.wait(lock, predicate): ロックを解放し、述語(predicate)が true になるか通知(notify)されるまで待機。cv.notify_one(): 待機しているスレッドの一つを起こす。cv.notify_all(): 待機している全てのスレッドを起こす。
std::atomic<T>(<atomic>): アトミック型。複数のスレッドから同時にアクセスされてもデータ競合を起こさない型(整数型、bool、ポインタなど)。ロックフリーな操作を提供することがある。std::async,std::future,std::promise(<future>): 非同期処理のための高レベルな機能。タスクの実行と結果の取得を分離できる。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <atomic>
#include <chrono> // sleep_for
std::mutex cout_mutex; // コンソール出力用のミューテックス (coutはスレッドセーフではない)
std::atomic<int> atomic_counter = 0; // アトミックなカウンター
int non_atomic_counter = 0;
std::mutex counter_mutex; // 非アトミックカウンター用のミューテックス
void worker_function(int id) {
// lock_guard を使ってコンソール出力を保護
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Worker thread " << id << " started. ID: "
<< std::this_thread::get_id() << std::endl;
}
// 時間のかかる処理をシミュレート
std::this_thread::sleep_for(std::chrono::milliseconds(10 * id));
// カウンターをインクリメント
for (int i = 0; i < 10000; ++i) {
atomic_counter++; // アトミック操作 (ロック不要)
// 非アトミックカウンターはロックで保護する必要がある
std::lock_guard<std::mutex> lock(counter_mutex);
non_atomic_counter++;
}
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Worker thread " << id << " finished." << std::endl;
}
}
int main() {
const int num_threads = 5;
std::vector<std::thread> threads;
std::cout << "Main thread ID: " << std::this_thread::get_id() << std::endl;
std::cout << "Starting " << num_threads << " worker threads..." << std::endl;
// スレッドを生成して開始
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back(worker_function, i + 1); // emplace_back で直接生成
// threads.push_back(std::thread(worker_function, i + 1)); // push_back でも可
}
std::cout << "Waiting for threads to complete..." << std::endl;
// 全てのスレッドの終了を待機 (join)
for (auto& t : threads) {
if (t.joinable()) { // join 可能かチェック
t.join();
}
}
std::cout << "\nAll threads finished." << std::endl;
std::cout << "Final atomic counter value: " << atomic_counter.load() << std::endl; // .load() で値を取得 (atomic<int>は直接coutも可)
std::cout << "Final non-atomic counter value: " << non_atomic_counter << std::endl;
// 両方とも 5 * 10000 = 50000 になるはず (保護されていれば)
return 0;
}
</int>
時間 (<chrono>) (C++11)
時間や時間間隔を扱うためのライブラリ。
- Clock (クロック): 時間の源泉。
std::chrono::system_clock: システムの壁時計時間。OS の設定変更で変化しうる。to_time_t,from_time_tで C のtime_tと相互変換可能。std::chrono::steady_clock: 単調増加するクロック。時間計測に適している (巻き戻らない)。std::chrono::high_resolution_clock: 利用可能な最も高い分解能のクロック (steady_clockのエイリアスの場合が多い)。
std::chrono::time_point<Clock, Duration>: 特定の時点を表す。クロックと、そのクロックのエポック(基準点)からの経過時間 (Duration) で定義される。std::chrono::duration<Rep, Period>: 時間間隔を表す。Repは内部表現の型 (例:int,double)、Periodは時間の単位 (例:std::ratio<1, 1000>でミリ秒)。- 便利な定義済み duration:
nanoseconds,microseconds,milliseconds,seconds,minutes,hours(C++14以降では days, weeks, months, years も<chrono>内に)。
- 便利な定義済み duration:
duration_cast<ToDuration>(d): ある duration を別の単位の duration に変換する。
#include <iostream>
#include <chrono>
#include <thread>
#include <ctime> // std::time_t, std::localtime
#include <iomanip> // std::put_time
int main() {
//--- 時間計測 ---
auto start = std::chrono::high_resolution_clock::now(); // または steady_clock
// 計測したい処理
std::this_thread::sleep_for(std::chrono::milliseconds(150));
long long sum = 0;
for(int i=0; i<1000000; ++i) sum += i; // 何か計算
auto end = std::chrono::high_resolution_clock::now();
// 経過時間 (duration) を計算
auto elapsed = end - start;
// 様々な単位で表示
long long ns = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
long long us = std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
long long ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
double s = std::chrono::duration<double>(elapsed).count(); // double で秒単位
std::cout << "Elapsed time:" << std::endl;
std::cout << " " << ns << " ns" << std::endl;
std::cout << " " << us << " us" << std::endl;
std::cout << " " << ms << " ms" << std::endl;
std::cout << " " << std::fixed << std::setprecision(6) << s << " s" << std::endl;
//--- 現在時刻の取得と表示 (system_clock) ---
auto now_sys = std::chrono::system_clock::now();
// time_t に変換して C スタイルで表示
std::time_t now_c = std::chrono::system_clock::to_time_t(now_sys);
// std::cout << "\nCurrent time (ctime): " << std::ctime(&now_c); // ctime は末尾に改行含む
// put_time でフォーマット指定して表示
std::tm now_tm = *std::localtime(&now_c); // スレッドセーフでない場合あり
std::cout << "Current time (put_time): "
<< std::put_time(&now_tm, "%Y-%m-%d %H:%M:%S") << std::endl;
// C++20: より簡単な時刻フォーマット (chronoヘッダ)
// std::cout << "Current time (C++20 format): " << std::format("{:%Y-%m-%d %H:%M:%S}", now_sys) << std::endl; // 要 <format>
//--- duration の操作 ---
std::chrono::seconds sec(5);
std::chrono::milliseconds msec = sec; // 暗黙変換可能 (秒 -> ミリ秒)
std::cout << "\n5 seconds is " << msec.count() << " milliseconds." << std::endl; // 5000
std::chrono::milliseconds duration1(2500);
// std::chrono::seconds sec_lossy = duration1; // エラー: 精度が失われる可能性のある変換は暗黙的にはできない
std::chrono::seconds sec_lossy = std::chrono::duration_cast<std::chrono::seconds>(duration1); // 明示的にキャスト (切り捨て)
std::cout << "2500 ms is " << sec_lossy.count() << " seconds (truncated)." << std::endl; // 2
auto total_duration = std::chrono::minutes(2) + std::chrono::seconds(30);
std::cout << "2 min + 30 sec = " << total_duration.count() << " seconds." << std::endl; // 150
return 0;
}
</format>
モダン C++ の便利な機能
C++11, C++14, C++17, C++20 で導入された主要な機能の一部。
std::optional<T>(C++17,<optional>): 値が存在しない可能性のある変数を表現する。ヌルポインタや特殊な値(-1など)を使わずに、値の有無を安全に扱える。opt.has_value()/if(opt): 値が存在するかチェック。opt.value(): 値を取得(存在しない場合はstd::bad_optional_access例外)。*opt/opt->member: 値にアクセス(存在しない場合は未定義動作 )。opt.value_or(default_value): 値があればそれを、なければデフォルト値を返す。
std::variant<Types...>(C++17,<variant>): 型安全な共用体 (union)。定義された型のいずれか一つの値を保持できる。var.index(): 現在保持している型のインデックスを取得。std::holds_alternative<T>(var): 特定の型 T を保持しているかチェック。std::get<T>(var)/std::get<I>(var): 特定の型 T またはインデックス I の値を取得(型が違う場合はstd::bad_variant_access例外)。std::visit(visitor, var1, var2...): ビジターパターン。variant が保持する型に応じて処理を振り分ける。
std::any(C++17,<any>): 任意の型の値を保持できる型安全なラッパー。a.has_value(): 値を保持しているか。a.type(): 保持している値のtype_infoを返す。std::any_cast<T>(&a): 値へのポインタを安全に取得(型が違う場合はnullptr)。std::any_cast<T>(a): 値をコピーまたは参照で取得(型が違う場合はstd::bad_any_cast例外)。
- 構造化束縛 (Structured Bindings) (C++17): 配列、
std::tuple,std::pair, 構造体などのメンバを個別の変数に分解して束縛する。std::pair<int std::string=""> p = {1, "one"}; auto [id, name] = p; // id = 1, name = "one" std::tuple<double bool=""> t = {3.14, 'a', true}; auto& [val, c, flag] = t; // 参照で束縛も可能 struct Point { double x, y; }; Point pt = {1.0, 2.0}; auto [x_coord, y_coord] = pt; </double></int> if constexpr(C++17): コンパイル時 if 文。テンプレート内で、テンプレートパラメータの型などに基づいて、コンパイル時に不要なコード分岐を除去できる。template<typename t=""> auto getValue(T v) { if constexpr (std::is_pointer_v<t>) { return *v; // ポインタなら間接参照 } else { return v; // そうでなければそのまま返す } } </t></typename>- ファイルシステムライブラリ (
<filesystem>) (C++17): ファイルやディレクトリの操作(パスの操作、存在確認、サイズ取得、コピー、削除、ディレクトリ走査など)をOSに依存しない形で行える。#include <filesystem> // C++17 namespace fs = std::filesystem; // エイリアス fs::path p = "/path/to/file.txt"; std::cout << "Filename: " << p.filename() << std::endl; // file.txt std::cout << "Parent path: " << p.parent_path() << std::endl; // /path/to if (fs::exists(p)) { /* ... */ } fs::create_directory("/new/dir"); for (const auto& entry : fs::directory_iterator("/some/dir")) { std::cout << entry.path() << std::endl; } </filesystem> - コンセプト (Concepts) (C++20,
<concepts>): テンプレートパラメータに対する制約を指定する機能。テンプレートのエラーメッセージが分かりやすくなり、インターフェースの意図が明確になる。#include <concepts> // C++20 // 整数のコンセプト template<typename t=""> concept Integral = std::is_integral_v<t>; // 加算可能なコンセプト template<typename t=""> concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<t>; // a + b が可能で、結果が T に変換可能か }; // コンセプトを使った関数テンプレート template<integral t=""> T gcd(T a, T b) { /* ... */ } template<addable t=""> T sum(T a, T b) { return a + b; } void example() { gcd(10, 5); // OK // gcd(10.5, 5.5); // コンパイルエラー (コンセプト Integral を満たさない) sum(1, 2); // OK sum(1.5, 2.5); // OK // sum(std::string("a"), 1); // コンパイルエラー (Addable を満たさない) } </addable></integral></t></typename></t></typename></concepts> - Ranges (C++20,
<ranges>): イテレータペアの代わりに「範囲 (range)」を直接扱えるようにするライブラリ。アルゴリズムの記述が簡潔になり、パイプライン演算子 (|) による処理の連結が可能になる。#include <vector> #include <ranges> // C++20 #include <iostream> namespace views = std::views; // エイリアス int main() { std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 偶数をフィルタリングし、2乗して、最初の3つを取得 auto results = nums | views::filter([](int n){ return n % 2 == 0; }) // 偶数のみ | views::transform([](int n){ return n * n; }) // 2乗 | views::take(3); // 最初の3つ std::cout << "Ranges result: "; for(int val : results) { // results は range オブジェクト std::cout << val << " "; // 4 16 36 } std::cout << std::endl; // 従来のアルゴリズムも ranges 版がある // std::ranges::sort(nums); return 0; } </int></iostream></ranges></vector> - モジュール (Modules) (C++20): ヘッダーファイル (
#include) の問題を解決するための新しいコード構成単位。コンパイル時間の短縮、マクロの影響範囲の限定などが期待される。 (コンパイラの対応が必要)// math.cppm (モジュールインターフェースユニット) export module math; // モジュール宣言 export int add(int a, int b) { // エクスポートする関数 return a + b; } // main.cpp (モジュールを利用する側) import math; // モジュールをインポート #include <iostream> int main() { std::cout << add(2, 3) << std::endl; // 5 return 0; } // コンパイル方法例 (g++): // g++ -std=c++20 -fmodules-ts math.cppm -c // g++ -std=c++20 -fmodules-ts main.cpp math.o -o main </iostream> - コルーチン (Coroutines) (C++20,
<coroutine>): 処理を中断・再開できる関数。非同期処理、ジェネレータなどをより簡潔に記述できる可能性がある。co_await,co_yield,co_returnキーワードを使用。ライブラリサポートが必要なことが多い。
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string>
#include <vector>
// std::optional の例
std::optional<std::string> find_user(int id) {
if (id == 1) return "Alice";
if (id == 2) return "Bob";
return std::nullopt; // 値がない場合は std::nullopt
}
// std::variant の例
using IntOrString = std::variant<int std::string="">;
void process_variant(const IntOrString& v) {
std::visit([](const auto& value) { // ラムダ式を使ったビジター
using T = std::decay_t<decltype>;
if constexpr (std::is_same_v<t int="">) {
std::cout << "Variant holds an int: " << value << std::endl;
} else if constexpr (std::is_same_v<t std::string="">) {
std::cout << "Variant holds a string: " << value << std::endl;
}
}, v);
}
int main() {
// optional
auto user1 = find_user(1);
if (user1) { // .has_value() と同じ
std::cout << "User 1 found: " << *user1 << std::endl; // Alice
// std::cout << "User 1 found: " << user1.value() << std::endl; // 同上 (例外送出の可能性あり)
}
auto user3 = find_user(3);
std::cout << "User 3 found: " << user3.value_or("Not Found") << std::endl; // Not Found
// variant
IntOrString v1 = 123;
IntOrString v2 = "Hello Variant";
process_variant(v1); // Variant holds an int: 123
process_variant(v2); // Variant holds a string: Hello Variant
if (std::holds_alternative<int>(v1)) {
std::cout << "v1 is int: " << std::get<int>(v1) << std::endl;
}
// any
std::any a1 = 10;
std::any a2 = std::string("Hello Any");
std::any a3 = 3.14;
std::cout << "a1 type: " << a1.type().name() << std::endl; // Implementation defined (e.g., 'i' for int)
std::cout << "a2 type: " << a2.type().name() << std::endl; // Implementation defined
try {
int val_a1 = std::any_cast<int>(a1);
std::cout << "a1 as int: " << val_a1 << std::endl; // 10
std::string val_a2 = std::any_cast<std::string>(a2);
std::cout << "a2 as string: " << val_a2 << std::endl; // Hello Any
int val_a3_fail = std::any_cast<int>(a3); // bad_any_cast 例外
} catch (const std::bad_any_cast& e) {
std::cerr << "any_cast failed: " << e.what() << std::endl;
}
// any_cast のポインタ版 (安全)
if (auto p_str = std::any_cast<std::string>(&a2)) {
std::cout << "a2 string pointer: " << *p_str << std::endl;
}
if (auto p_int = std::any_cast<int>(&a2)) {
// これは nullptr になる
std::cout << "a2 int pointer is null" << std::endl;
} else {
std::cout << "a2 is not an int (checked with pointer cast)." << std::endl;
}
return 0;
}
</int></std::string></int></std::string></int></int></int></t></t></decltype></int></std::string></vector></string></any></variant></optional></iostream>