Skip to content

C++ チートシート

OOP、ジェネリクス、低レベル制御を持つ汎用言語。

01

はじめに

Hello World

すべての C++ プログラムは main() から始まります。<iostream> は std::cout(標準出力)と std::cin(標準入力)を提供します。std:: プレフィックスは標準名前空間を参照します;using namespace std; で回避できますが、グローバル名前空間を汚染するためヘッダでは推奨されません。

cpp
#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

変数と auto

auto は初期化子から型を推論します(C++11)。型が明らかまたは冗長な場合(イテレータ)に auto を使用します。const は値を不変にし;constexpr はコンパイル時に評価し、バイナリに埋め込まれる真の定数にします。

cpp
int age = 30;
double pi = 3.14159;
char grade = 'A';
bool is_dev = true;
std::string name = "Alice";
auto x = 42;          // int
auto y = 3.14;        // double
const double TAX = 0.08;

入力と出力

std::getline はスペースを含む完全な行を読み取りますが、std::cin >> は空白で停止します。両者を混用するとバッファに改行が残ります;getline と >> の間に std::cin.ignore() を呼び出して破棄してください。

cpp
#include <iostream>
#include <string>

int main() {
    std::string name;
    int age;
    std::cout << "Name: ";
    std::getline(std::cin, name);
    std::cout << "Age: ";
    std::cin >> age;
    std::cout << "Hi " << name << ", " << age << "\n";
}

参照

参照は初期化時に必ず割り当てられ、別のオブジェクトに再割り当てできないエイリアスです。参照渡しはコピーを回避し、呼び出し側の変数を変更できます。高コストなコピーを避けるため、読み取り専用パラメータには const T& を使用します。

cpp
int x = 10;
int &ref = x;   // alias for x
ref = 20;
std::cout << x;   // 20

void increment(int &n) { n++; }
int a = 5;
increment(a);   // a is now 6

型変換

C スタイルのキャストより static_cast を優先してください — 明示的でコンパイル時にチェックされ、意図が明確になります。std::stoi、std::stod は文字列を数値に変換;std::to_string は逆を行います。不正入力での std::out_of_range に注意してください。

cpp
double d = 3.99;
int i = (int)d;               // C-style: 3
int j = static_cast<int>(d);  // C++: 3

int n = 65;
char c = static_cast<char>(n);  // 'A'

// string <-> number
int num = std::stoi("42");
std::string s = std::to_string(3.14);
02

文字列

std::string の基礎

std::string は自身のメモリを管理し、必要に応じて成長します。C の char 配列とは異なり、長さを手動管理しません。.find() は部分文字列が見つからない場合 std::string::npos(巨大な値)を返すため、常に npos と比較してください。

cpp
#include <string>
std::string s = "Hello";
std::cout << s.length();      // 5
std::cout << s[0];            // H
s += ", World!";
std::cout << s;               // Hello, World!
std::cout << s.substr(0, 3);  // Hel
std::cout << s.find("World"); // 7

比較と検索

比較は辞書順です。.find() は前方検索、.rfind() は後方検索します。両方とも見つからない場合 std::string::npos を返すため、結果を boolean として扱うのではなく常に npos と比較してください。

cpp
std::string a = "apple", b = "banana";
if (a == b) { /* equal */ }
if (a < b) { /* apple comes before banana */ }
if (a.find("pp") != std::string::npos) {
    std::cout << "found\n";
}
size_t pos = a.rfind("p");  // last occurrence

Stringstream

stringstream は文字列と型付き値の橋渡しをし、フォーマットされた文字列の構築(バッファのような)や空白区切りのトークンの解析に便利です。直接操作より遅いですが、シリアライズとデシリアライズに非常に柔軟です。

cpp
#include <sstream>
// Build a string
std::ostringstream oss;
oss << "Name=" << "Alice" << "&Age=" << 30;
std::string result = oss.str();

// Parse tokens
std::istringstream iss("10 20 30");
int a, b, c;
iss >> a >> b >> c;   // a=10, b=20, c=30

Raw 文字列と複数行

Raw 文字列リテラル R"(...)" はバックスラッシュと引用符を文字通りに扱い、正規表現パターン、Windows ファイルパス、JSON/XML テンプレートに最適です。括弧内の区切り文字は任意で、例えば R"x(...)x" で ) を内部に含められます。

cpp
std::string raw = R"(C:\Users\name\file.txt)";
// No need to escape backslashes

std::string json = R"({
  "name": "Alice",
  "age": 30
})";

char 配列と std::string

C スタイルの char 配列は手動サイズ管理が必要でエラーが起きやすいです(バッファオーバーフロー)。std::string を優先してください;const char* を期待する C API とのインターフェースには .c_str() を使用します。c_str() は文字列が存続し未変更の間のみ有効なことに注意してください。

cpp
char cstr[] = "Hello";   // null-terminated, size 6
std::string cppstr = "Hello";

#include <cstring>
std::cout << strlen(cstr);      // 5
std::cout << cppstr.length();   // 5

std::string from_c = cstr;          // C -> C++
const char* to_c = cppstr.c_str();  // C++ -> C
03

数値と数学

整数型と浮動小数点型

正確なサイズがプラットフォーム間で重要な場合は <cstdint> 固定幅型(int32_t、int64_t)を使用します。' 桁区切り(C++14)は大きな数値の可読性を向上させます。double がデフォルトの浮動小数点型で、精度のために float より推奨されます。

cpp
#include <cstdint>
int32_t a = 100;
int64_t big = 9'000'000'000LL;
uint8_t byte = 255;
size_t sz = sizeof(a);   // 4

float f = 1.5f;          // 4 bytes
double d = 3.14159265;   // 8 bytes (preferred)

数値制限

<limits> は数値プロパティの型特性を提供します。ハードコードされた INT_MAX マクロの代わりにこれらを使用してください。epsilon() は浮動小数点で区別できる最小の差を与え、許容値付きで double を比較するのに便利です。

cpp
#include <limits>
std::cout << std::numeric_limits<int>::max();    // 2147483647
std::cout << std::numeric_limits<int>::min();    // -2147483648
std::cout << std::numeric_limits<double>::infinity();
std::cout << std::numeric_limits<double>::epsilon();

数学関数

<cmath> は標準数学関数を提供します。C++ で整数オーバーフローは未定義動作です — int64_t を使用するか境界をチェックしてください。金融コードでは、浮動小数点は不正確なため — 整数セントまたは decimal ライブラリを検討してください。

cpp
#include <cmath>
double x = 2.5;
std::pow(x, 3);     // 15.625
std::sqrt(x);       // 1.581
std::abs(-5);       // 5
std::floor(3.7);    // 3.0
std::ceil(3.2);     // 4.0
std::round(3.5);    // 4.0
std::fmod(10.5, 3); // 1.5

乱数

モダン C++ は rand() の代わりに <random> ライブラリを使用します。mt19937 は高品質な PRNG です。分布(uniform_int、uniform_real、normal)は rand() % N を悩ませるモジュロバイアスなしに生ビットを目的の範囲にマッピングします。

cpp
#include <random>
std::random_device rd;
std::mt19937 gen(rd());  // Mersenne Twister engine
std::uniform_int_distribution<int> dist(1, 100);

for (int i = 0; i < 5; i++) {
    std::cout << dist(gen) << " ";
}

整数オーバーフローとキャスト

符号付き整数オーバーフローは C++ で未定義動作です(コンパイラは発生しないと仮定して最適化する可能性があります)。乗算前に常に広い型にキャストするか、境界をチェックしてください。符号なしオーバーフローは 2^n を法としてラップアラウンドし、よく定義されています。

cpp
int a = 100'000;
int b = a * a;   // overflow! undefined behavior
long long safe = (long long)a * a;  // OK

// Check before multiplying
if (a > 0 && b > INT_MAX / a) {
    // would overflow, handle it
}
04

制御フロー

If / Else

C++17 は初期化子付き if を導入しました:if (auto it = m.find(k); it != m.end()) { ... }。これにより変数が if/else ブロックにスコープされ、周囲のスコープをクリーンに保ち、誤った再利用を防ぎます。

cpp
int score = 85;
if (score >= 90) {
    std::cout << "A\n";
} else if (score >= 80) {
    std::cout << "B\n";
} else {
    std::cout << "C\n";
}

Switch

意図しないフォールスルーを防ぐため常に break を含めてください。C++17 の [[fallthrough]] 属性は意図的なフォールスルーを文書化し警告を抑制します。switch は整数型と enum 型で動作し、文字列や float では動作しません。

cpp
int day = 3;
switch (day) {
    case 1: std::cout << "Mon"; break;
    case 2: std::cout << "Tue"; break;
    case 3: std::cout << "Wed"; break;
    default: std::cout << "Other";
}

For ループ

範囲ベース for はコンテナをクリーンに反復します。要素のコピーを避けるため const auto& を使用します(文字列や大きなオブジェクトに重要)。要素をその場で変更するには auto&(非 const 参照)を使用します。

cpp
// Classic for
for (int i = 0; i < 5; i++) {
    std::cout << i << " ";
}

// Range-based for (C++11)
std::vector<int> v = {1, 2, 3};
for (int n : v) std::cout << n;
for (const auto &n : v) std::cout << n;  // no copy

While と Do-While

while は実行前に条件をチェック;do-while はチェック前に本体を少なくとも1回実行します。do-while は入力検証とメニューループに便利です — 本体が条件評価の前に実行される必要があるため。

cpp
int n = 5;
while (n > 0) {
    std::cout << n-- << " ";
}

int x;
do {
    std::cin >> x;
} while (x < 0);  // runs at least once

Break、Continue とネストされたループ

break は最も近い囲むループを終了;continue は次の反復にスキップします。C++ には Java のようなラベル付き break がありません;フラグ変数を使用するか、ループを関数に抽出して return でネストループから抜けます。

cpp
for (int i = 0; i < 10; i++) {
    if (i == 3) continue;  // skip 3
    if (i == 7) break;     // stop at 7
    std::cout << i << " ";  // 0 1 2 4 5 6
}

// C++ has no labeled break; use a flag
bool found = false;
for (int i = 0; i < n && !found; i++) {
    for (int j = 0; j < m; j++) {
        if (matrix[i][j] == target) { found = true; break; }
    }
}
05

関数とラムダ

定義と複数戻り値

C++17 の構造化束縛(auto [a, b] = ...)はタプル、ペア、構造体をクリーンに展開します。C++17 以前は std::tie または出力パラメータを使用します。値渡しでの戻りはコピー省略(RVO)により安価です。

cpp
#include <tuple>
std::tuple<int, int> divide(int a, int b) {
    return {a / b, a % b};
}

auto [q, r] = divide(17, 5);  // q=3, r=2 (C++17)

デフォルトと inline

デフォルト引数により呼び出し側は末尾パラメータを省略できます。inline はコンパイラに関数をインライン展開するヒントです;モダンコンパイラは最適化フラグに基づいて自身で決定するため、inline は主に ODR(単一定義規則)に関するものです。

cpp
inline int power(int base, int exp = 2) {
    int r = 1;
    for (int i = 0; i < exp; i++) r *= base;
    return r;
}
// power(3) == 9, power(2, 5) == 32

関数のオーバーロード

オーバーロードにより関数は名前を共有できますがパラメータ型で異なります。コンパイラはオーバーロード解決で最適なマッチを選びます。曖昧なオーバーロードはコンパイルエラーを引き起こします;本体が型間で同一の場合はテンプレートを優先してください。

cpp
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
std::string add(std::string a, std::string b) { return a + b; }

add(1, 2);          // int version
add(1.5, 2.5);      // double version
add("a", "b");      // string version

ラムダ式

ラムダはインラインで匿名関数オブジェクトを作成します。[] は変数をキャプチャ:[=] 値で、[&] 参照で、[x] 特定を値で、[&x] 特定を参照で。STL アルゴリズムとコールバックに不可欠です。参照キャプチャ時のダングリング参照に注意してください。

cpp
auto square = [](int x) { return x * x; };
std::cout << square(5);  // 25

int factor = 3;
auto multiply = [factor](int x) { return x * factor; };

std::vector<int> v = {1, 2, 3};
std::for_each(v.begin(), v.end(), [](int n) {
    std::cout << n << " ";
});

関数ポインタと std::function

std::function(<functional> から)は任意の callable を保持します:関数、ラムダ、ファンクタ。生の関数ポインタより柔軟ですが、型消去のための小さなランタイムオーバーヘッドがあります。コールバックとコンテナへの callable 格納に使用します。

cpp
#include <functional>
int add(int a, int b) { return a + b; }

int (*fp)(int, int) = add;             // raw function pointer
std::function<int(int, int)> f = add;  // flexible wrapper
f = [](int a, int b) { return a * b; };

std::cout << f(3, 4);  // 12
06

STL コンテナ

vector

vector は動的配列でデフォルトのコンテナ選択です。push_back は償却 O(1) です。.at() は境界チェックを行い(std::out_of_range をスロー)、operator[] は行いません。サイズが分かっている場合は再割り当てを避けるため事前に reserve() を呼び出してください。

cpp
#include <vector>
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.pop_back();
v.size();          // 3
v[0] = 0;
v.at(5);           // throws std::out_of_range
for (int n : v) std::cout << n;

map と unordered_map

map はキーをソート済みに保ちます(O(log n) 操作);unordered_map はハッシュを使用します(平均 O(1))。順序付き反復または範囲クエリが必要な場合は map を;純粋なルックアップ速度には unordered_map を使用します。unordered_map の反復は順不同です。

cpp
#include <map>
#include <unordered_map>
std::map<std::string, int> ordered;   // sorted by key (red-black tree)
ordered["Alice"] = 30;

std::unordered_map<std::string, int> hashed;  // hash table
hashed["Bob"] = 25;

for (auto &[k, v] : ordered) {  // C++17 structured binding
    std::cout << k << ":" << v;
}

set と unordered_set

set はユニークなソート済み要素を格納します(O(log n))。unordered_set はハッシュベース版です(平均 O(1))。重複排除とメンバーシップテストに使用します。lower_bound/upper_bound で順序付きセットの範囲クエリが可能です。

cpp
#include <set>
std::set<int> s = {3, 1, 4, 1, 5};
// s contains: 1, 3, 4, 5 (sorted, unique)
s.insert(2);
s.erase(1);
if (s.count(4)) std::cout << "found";
auto it = s.lower_bound(3);  // first >= 3

array と deque

array は固定サイズのスタック割り当て配列で STL インターフェースを持ちます(C 配列より安全、ポインタへの崩壊なし)。deque(両端キュー)は両端での O(1) push/pop をサポートし、先頭が O(n) の vector とは異なります。

cpp
#include <array>
#include <deque>
std::array<int, 3> arr = {1, 2, 3};  // fixed size, stack-allocated
arr.size();  // 3

std::deque<int> dq = {1, 2, 3};
dq.push_front(0);
dq.push_back(4);
// dq: 0, 1, 2, 3, 4

tuple と pair

tuple は任意の型の異種値を保持します。pair は2要素タプルです。構造化束縛(C++17)で名前付き変数に分解します。要素が(キー、値)のペアである map の反復時に一般的です。

cpp
#include <tuple>
std::tuple<int, std::string, double> t = {1, "Alice", 3.14};
auto [id, name, val] = t;  // C++17 structured binding

std::pair<int, int> p = {1, 2};
std::cout << p.first << p.second;

auto [a, b] = std::make_pair(10, 20);
07

ポインタとメモリ

生ポインタ

ポインタはメモリアドレスを格納します。& はアドレスを取得し、* はデリファレンスします。ポインタ演算は配列で動作します。生ポインタは所有権を追跡せず、リークやダングリングポインタを引き起こします — 所有リソースにはスマートポインタを優先してください。

cpp
int x = 10;
int *ptr = &x;        // address of x
std::cout << *ptr;    // 10 (dereference)
*ptr = 20;
std::cout << x;       // 20

int arr[] = {1, 2, 3};
int *p = arr;
std::cout << *(p + 1);  // 2

参照とポインタ

参照はより安全(null にならず、常に有効)でクリーンな構文を持ちます。関数パラメータと戻り値には参照を使用します。null が意味のある状態の場合や、指す先を再割り当てする必要がある場合にポインタを使用します。

cpp
int x = 10;
int &ref = x;    // must init, cannot reseat
int *ptr = &x;   // can be null, can reassign

ref = 20;        // x = 20
*ptr = 30;       // x = 30

// References cannot be null, safer for parameters
void foo(const std::string &s);  // preferred
void bar(std::string *s);        // s might be null

unique_ptr

unique_ptr はヒープオブジェクトの単独所有権です。コピーできず、ムーブのみ可能です。スコープ外に出ると自動的に削除します(RAII)。これがほとんどのユースケースのデフォルトスマートポインタです — 生ポインタに対してゼロオーバーヘッド。

cpp
#include <memory>
auto p = std::make_unique<int>(42);
std::cout << *p;  // 42
// auto p2 = p;   // ERROR: cannot copy
auto p2 = std::move(p);  // transfer ownership
// p is now nullptr

shared_ptr と weak_ptr

shared_ptr は参照カウントを使用;最後の shared_ptr が破棄されるとオブジェクトが解放されます。weak_ptr はカウントに影響せずに観察し、参照サイクルを断ち切ります。shared_ptr のサイクルは避けてください(カウントがゼロに到達しないためリークします)。

cpp
#include <memory>
auto a = std::make_shared<int>(42);
auto b = a;  // both point to same object
std::cout << a.use_count();  // 2

std::weak_ptr<int> w = a;  // observer, no ownership
if (auto locked = w.lock()) {
    std::cout << *locked;  // 42
}

RAII と new/delete

RAII はリソースライフタイムをオブジェクトスコープに結び付けます:コンストラクタが取得し、デストラクタが解放します。これにより例外が伝播してもクリーンアップが保証されます。手動 new/delete より vector とスマートポインタを優先してください — それらが RAII を実装します。

cpp
// Manual new/delete (avoid in modern C++)
int *p = new int(42);
delete p;

// RAII: resource acquisition is initialization
class Buffer {
    int *data;
public:
    Buffer(size_t n) : data(new int[n]) {}
    ~Buffer() { delete[] data; }  // auto cleanup
};

// Modern: use containers/smart pointers instead
std::vector<int> buf(100);  // no manual delete needed
08

クラスと OOP

クラスとコンストラクタ

メンバ初期化リスト(: name(...)、age(...))は本体実行前にメンバを初期化し、本体内での代入より効率的です。getter に const を付け、const オブジェクトでの呼び出しを許可します。std::move で文字列パラメータのコピーを回避します。

cpp
class Person {
    std::string name;
    int age;
public:
    Person(std::string n, int a) : name(std::move(n)), age(a) {}
    std::string getName() const { return name; }
    int getAge() const { return age; }
};

Person p("Alice", 30);
std::cout << p.getName();

アクセス修飾子とカプセル化

private メンバはクラス内のみアクセス可能;protected はサブクラスを許可;public はすべてに公開。カプセル化は実装の詳細を隠し、安定したインターフェースを公開します。デフォルトで private にし、必要なもののみ公開してください。

cpp
class Account {
private:
    double balance;
protected:
    std::string owner;
public:
    Account(double b) : balance(b) {}
    double getBalance() const { return balance; }
    void deposit(double amt) { if (amt > 0) balance += amt; }
};

継承と virtual

virtual はランタイムポリモーフィズムを可能にします — Animal* 経由で speak() を呼び出すと Dog のバージョンにディスパッチします。ベースクラスには常に仮想デストラクタを宣言し、ベースポインタ経由の削除で派生デストラクタが呼ばれるようにしてください。override でタイプミスをキャッチします。

cpp
class Animal {
public:
    virtual void speak() { std::cout << "..."; }
    virtual ~Animal() = default;  // virtual destructor!
};

class Dog : public Animal {
public:
    void speak() override { std::cout << "Woof"; }
};

Animal *a = new Dog();
a->speak();  // Woof (polymorphism)
delete a;

抽象クラスとインターフェース

純粋仮想関数(= 0)はクラスを抽象的にします — インスタンス化できません。純粋仮想関数のみを持つクラスは Java のインターフェースのように動作します。具象サブクラスはすべての純粋仮想関数を実装するか、抽象のままです。

cpp
class Shape {
public:
    virtual double area() const = 0;  // pure virtual
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double r;
public:
    Circle(double r) : r(r) {}
    double area() const override {
        return 3.14159 * r * r;
    }
};

演算子オーバーロード

演算子オーバーロードによりユーザー型が +、<<、== などで動作できます。意味が直感的な場合(数学型、イテレータ)のみオーバーロードしてください。<< 演算子は friend 経由でストリーム出力によくオーバーロードされ、cout << myObject を可能にします。

cpp
class Vec2 {
    double x, y;
public:
    Vec2(double x, double y) : x(x), y(y) {}
    Vec2 operator+(const Vec2 &o) const {
        return {x + o.x, y + o.y};
    }
    friend std::ostream &operator<<(std::ostream &os, const Vec2 &v) {
        return os << "(" << v.x << "," << v.y << ")";
    }
};

Vec2 a(1, 2), b(3, 4);
std::cout << a + b;  // (4,6)
09

テンプレートとジェネリクス

関数テンプレート

関数テンプレートはコンパイル時に型固有のバージョンを生成します。コンパイラは引数から T を推論します;明示的に指定もできます。テンプレートはゼロコスト抽象です — ランタイムオーバーヘッドなしですが、コンパイル時間とバイナリサイズが増加します。

cpp
template <typename T>
T max_val(T a, T b) {
    return (a > b) ? a : b;
}

std::cout << max_val(3, 7);           // int: 7
std::cout << max_val(3.14, 2.71);     // double: 3.14
std::cout << max_val<std::string>("a", "b");  // explicit

クラステンプレート

クラステンプレートは型全体でクラスをパラメータ化します。標準コンテナ(vector、map)はすべてテンプレートです。コンパイラがコード生成に完全な定義を必要とするため、テンプレートコードはヘッダに置く必要があります(または明示的インスタンス化を使用)。

cpp
template <typename T>
class Stack {
    std::vector<T> data;
public:
    void push(T v) { data.push_back(v); }
    T pop() { T v = data.back(); data.pop_back(); return v; }
    bool empty() const { return data.empty(); }
};

Stack<int> si;
si.push(1); si.push(2);
Stack<std::string> ss;
ss.push("hi");

テンプレートの特殊化

完全特殊化は特定の型にカスタム実装を提供します。部分特殊化(クラステンプレートのみ)は型のカテゴリ(例:すべてのポインタ型)にカスタマイズします。最適化や特別扱いに便利です。

cpp
template <typename T>
T identity(T x) { return x; }

// Full specialization for bool
template <>
bool identity<bool>(bool x) {
    std::cout << "bool!";
    return x;
}

identity(42);     // generic
identity(true);   // specialized

可変長テンプレート

可変長テンプレートはパラメータパック(...)経由で任意の数の引数を受け取ります。各引数を処理するために再帰します。C++17 のフォールド式で簡素化されます:(std::cout << ... << args)。std::make_shared、std::tuple で多用されます。

cpp
template <typename T>
void print(T v) { std::cout << v << "\n"; }

template <typename T, typename... Args>
void print(T first, Args... rest) {
    std::cout << first << ", ";
    print(rest...);  // recurse
}

print(1, "hello", 3.14);  // 1, hello, 3.14

コンセプト(C++20)

コンセプト(C++20)は読みやすい要件でテンプレートパラメータを制約し、難解な enable_if/SFINAE を置き換えます。制約が満たされない場合、はるかに明確なエラーメッセージを生成します。std::integral、std::floating_point、std::convertible_to のような標準コンセプトを使用してください。

cpp
#include <concepts>
template <std::integral T>
T add(T a, T b) { return a + b; }

template <typename T>
requires std::floating_point<T>
T half(T x) { return x / 2; }

// Abbreviated form
auto square(std::integral auto x) { return x * x; }
10

STL アルゴリズム

sort と find

STL アルゴリズムはイテレータ範囲 [begin, end) で動作します。sort は O(n log n) です。find は線形;ソート済み範囲には binary_search/lower_bound(O(log n))を使用します。カスタム順序付けにはカスタムコンパレータ(ラムダまたは std::greater)を渡します。

cpp
#include <algorithm>
std::vector<int> v = {3, 1, 4, 1, 5, 9};
std::sort(v.begin(), v.end());          // 1 1 3 4 5 9
std::sort(v.begin(), v.end(), std::greater<>());  // descending

auto it = std::find(v.begin(), v.end(), 4);
if (it != v.end()) std::cout << "found";

transform と for_each

transform は各要素を新しい値にマッピングします(関数型言語の map のように)。for_each は副作用に関数を適用します。C++20 の ranges は begin/end イテレータなしでよりクリーンなパイプラインスタイルの v | views::transform(...) を可能にします。

cpp
std::vector<int> v = {1, 2, 3, 4};
std::vector<int> squared(v.size());
std::transform(v.begin(), v.end(), squared.begin(),
               [](int x) { return x * x; });
// squared: 1 4 9 16

std::for_each(v.begin(), v.end(), [](int &x) { x *= 2; });
// v: 2 4 6 8

accumulate と count

accumulate(<numeric> から)は演算で範囲をフォールドします。第3引数が初期値で結果型を決定します — double 合計には 0.0 を使用します。count は値に等しい要素数を返します;count_if は述語を使用します。

cpp
#include <numeric>
std::vector<int> v = {1, 2, 3, 4, 5};
int sum = std::accumulate(v.begin(), v.end(), 0);  // 15
int product = std::accumulate(v.begin(), v.end(), 1,
                              std::multiplies<>());  // 120

int cnt = std::count(v.begin(), v.end(), 3);  // 1

copy、remove と unique

remove は実際には消去しません — マッチしない要素を前にシフトし新しい終端イテレータを返します。erase-remove イディオムのために .erase() とペアにします。unique は同様に連続重複を圧縮します;完全に重複排除するには先に sort してください。

cpp
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> dest;
std::copy(v.begin(), v.end(), std::back_inserter(dest));

auto end = std::remove(v.begin(), v.end(), 3);  // erase-remove idiom
v.erase(end, v.end());

std::vector<int> u = {1, 1, 2, 2, 3};
u.erase(std::unique(u.begin(), u.end()), u.end());  // 1 2 3

min、max と clamp

min/max は2つの値または初期化リストの小さい方/大きい方を返します。minmax は両方をペアで返します。clamp(C++17)は値を範囲に制限し、手動の if/else 境界チェックを置き換えます — 入力検証と UI 座標に便利です。

cpp
int a = 3, b = 7;
std::cout << std::max(a, b);   // 7
std::cout << std::min(a, b);   // 3

auto [mn, mx] = std::minmax({3, 1, 4, 1, 5});  // mn=1, mx=5

int score = 105;
int clamped = std::clamp(score, 0, 100);  // 100
11

エラー処理

例外:try/catch

値で例外をスローし、スライスを避けるため const 参照でキャッチします。std::exception をキャッチするとベースクラス経由ですべての標準例外をキャッチします。例外は通常の制御フローではなく例外的なケース用です — スロー時にオーバーヘッドがあります。

cpp
#include <stdexcept>
try {
    int x = 10, y = 0;
    if (y == 0) throw std::runtime_error("division by zero");
    std::cout << x / y;
} catch (const std::exception &e) {
    std::cerr << "Error: " << e.what();
}

標準例外階層

<stdexcept> は標準例外型を提供します。logic_error はプログラマエラー用(実行前に検出可能);runtime_error は予期しない実行時条件用です。標準 catch ブロックと統合するため、カスタム例外を std::runtime_error から派生させてください。

cpp
#include <stdexcept>
throw std::runtime_error("runtime error");
throw std::logic_error("logic error");
throw std::out_of_range("index out of range");
throw std::invalid_argument("bad argument");
throw std::bad_alloc();  // thrown by new on allocation failure

// std::exception is the base; .what() returns the message

カスタム例外

カスタム例外を標準ベースから派生させ、catch(const std::exception&) と統合します。デバッグに役立つコンテキストフィールド(ファイルパス、エラーコード)を追加します。what() が動作するよう、常にメッセージをベースコンストラクタに渡してください。

cpp
class FileError : public std::runtime_error {
public:
    FileError(const std::string &msg, const std::string &path)
        : std::runtime_error(msg + ": " + path), path_(path) {}
    const std::string &path() const { return path_; }
private:
    std::string path_;
};

try { throw FileError("not found", "data.txt"); }
catch (const FileError &e) {
    std::cerr << e.what() << " at " << e.path();
}

noexcept と RAII の安全性

noexcept は関数がスローしないことを約束し、コンパイラ最適化を可能にします。スローした場合、std::terminate が呼ばれます。RAII はスタック巻き戻し中にデストラクタが実行されることを保証し、例外がコールスタックを伝播してもリソースが解放されます。

cpp
void safe_op() noexcept {
    // guaranteed not to throw
}

void risky() {
    std::vector<int> v(1000);  // RAII: cleanup on exception
    throw std::runtime_error("oops");
    // v's destructor still runs during stack unwinding
}

アサーション

assert() はデバッグビルドで条件をチェックします;リリース(NDEBUG 定義時)では削除されるため、本番チェックには使用しないでください。バグを示す内部不変条件に使用します。ユーザー向け検証には、例外をスローするかエラーコードを返してください。

cpp
#include <cassert>
double sqrt_safe(double x) {
    assert(x >= 0 && "sqrt of negative");
    return std::sqrt(x);
}
// In release builds (NDEBUG defined), assert is removed
12

ファイル I/O とストリーム

ファイルの読み込み

ifstream は読み取り用にファイルを開きます。オープン成功を常に確認してください(!file は失敗時に true と評価)。getline はスペースを含む行単位で読み取ります。ストリームのデストラクタが自動的にファイルを閉じます(RAII)ので、手動クローズは不要です。

cpp
#include <fstream>
#include <string>
std::ifstream file("input.txt");
if (!file) { std::cerr << "cannot open"; return 1; }

std::string line;
while (std::getline(file, line)) {
    std::cout << line << "\n";
}

ファイルの書き込み

ofstream はファイルに書き込み、デフォルトで切り詰めます。追記には std::ios::app を、バイナリモードには std::ios::binary を使用します。<< 演算子は std::cout と全く同じように動作します。out.flush() または std::endl(フラッシュもする)でフラッシュします。

cpp
#include <fstream>
std::ofstream out("output.txt");
if (!out) return 1;

out << "Line 1\n";
out << "Value: " << 42 << "\n";

out.close();  // optional, destructor closes
// Append mode: std::ofstream("f.txt", std::ios::app);

文字列ストリーム

ostringstream は混合型から文字列を構築します(バッファのように)。istringstream は文字列を型付き値に解析します。直接文字列操作より遅いですが、シリアライズ、URL 構築、トークン解析に非常に便利です。

cpp
#include <sstream>
// Build a string from mixed types
std::ostringstream oss;
oss << "Name=" << "Alice" << "&Age=" << 30;
std::string url = oss.str();

// Parse tokens from a string
std::istringstream iss("10 20 30");
int a, b, c;
iss >> a >> b >> c;

バイナリファイル

バイナリモードは改行変換を回避し、テキストよりコンパクトです。write/read は char* とバイト数を取ります — 構造体には reinterpret_cast を使用します。注意:バイナリファイルはアーキテクチャ間でポータブルではありません(エンディアン、構造体パディングが異なります)。

cpp
#include <fstream>
struct Record { int id; double value; };

// Write
std::ofstream out("data.bin", std::ios::binary);
Record r{1, 3.14};
out.write(reinterpret_cast<char*>(&r), sizeof(r));

// Read
std::ifstream in("data.bin", std::ios::binary);
Record r2;
in.read(reinterpret_cast<char*>(&r2), sizeof(r2));

フォーマット出力(C++20 fmt)

std::format(C++20)は Python スタイルのフォーマット文字列を C++ にもたらし、厄介な iomanip マニピュレータを置き換えます。古いコードには <iomanip> が setprecision、setw、setfill を提供します。{fmt} ライブラリは同じ構文の C++20 以前の人気のある代替です。

cpp
#include <format>  // C++20
std::string s = std::format("Hello, {}! You are {}.", "Alice", 30);
std::cout << std::format("{:.2f}", 3.14159);  // 3.14
std::cout << std::format("{:>10}", "right");   // padded

// Pre-C++20: use iomanip
#include <iomanip>
std::cout << std::fixed << std::setprecision(2) << 3.14159;
13

スマートポインタ

unique_ptr - 排他的所有権

unique_ptr はデフォルトのスマートポインタです — 所有者が1人で十分な場合に使用します。生ポインタに対してゼロオーバーヘッドです。make_unique が推奨されます(例外安全)。コピーできず、ムーブのみ可能。カスタムデリータで FILE* やソケットのような C リソースの RAII を可能にします。

cpp
#include <memory>
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1;  // ERROR: cannot copy
std::unique_ptr<int> p2 = std::move(p1);  // OK: transfer ownership
// p1 is now nullptr

// Custom deleter
auto deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> fp(fopen("f.txt", "r"), deleter);

shared_ptr - 共有所有権

shared_ptr は参照カウントを使用 — 複数のポインタが同じオブジェクトを所有できます。参照カウントが 0 になるとオブジェクトは破棄されます。make_shared が推奨されます(オブジェクト + 制御ブロックの単一割り当て)。アトミック参照カウントと制御ブロックのため unique_ptr より重いです。所有権が真に共有される場合に使用します。

cpp
#include <memory>
auto p1 = std::make_shared<int>(42);
auto p2 = p1;  // OK: both share ownership
std::cout << *p1 << " " << p1.use_count();  // 42 2

// Control block holds refcount + deleter + allocator
// Refcount is atomic (thread-safe), but object access is NOT
std::shared_ptr<int> p3{new int{10}};  // uses non-array new

// Thread-safe: refcount operations are atomic
// NOT thread-safe: accessing the pointed-to object

weak_ptr - サイクルの切断

weak_ptr は shared_ptr の非所有観察者です。参照カウントを増やしません。lock() で一時的に shared_ptr を取得します(オブジェクトが破棄済みの場合は null を返す)。参照サイクル(例:双方向リスト、親子関係)を断ち切り、メモリリークを防ぐために不可欠です。

cpp
#include <memory>
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev;  // weak to avoid cycle
};

auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a;  // weak_ptr from shared_ptr

// To use a weak_ptr, lock() into a shared_ptr
if (auto locked = b->prev.lock()) {
    std::cout << "prev exists";
} else {
    std::cout << "prev expired";
}

配列付きスマートポインタ

スマートポインタは配列を管理できます。unique_ptr<T[]> は operator[] と正しい delete[] を提供します。shared_ptr<T[]> は C++17 からサポートされています。ただし、std::vector や std::array がほぼ常に優れています — より安全で使いやすく、自己文書化されています。レガシー API とのインターフェース時にのみスマート配列ポインタを使用してください。

cpp
#include <memory>
// C++17: shared_ptr supports arrays
std::shared_ptr<int[]> arr1(new int[10]);
arr1[0] = 42;

// unique_ptr with arrays (partial specialization exists)
std::unique_ptr<int[]> arr2 = std::make_unique<int[]>(10);
arr2[5] = 100;

// Prefer std::array or std::vector over raw arrays
#include <vector>
std::vector<int> v(10);  // better choice

enable_shared_from_this

オブジェクトが自身への shared_ptr を返す必要がある場合、enable_shared_from_this が安全な shared_from_this() を提供します。shared_ptr<T>(this) を直接呼び出すと2番目の制御ブロックが作成され、二重解放につながります。オブジェクトは既に shared_ptr で管理されている必要があり、そうでないと shared_from_this() は bad_weak_ptr をスローします。

cpp
#include <memory>
class Widget : public std::enable_shared_from_this<Widget> {
public:
    std::shared_ptr<Widget> getPtr() {
        return shared_from_this();  // safe
        // return shared_ptr<Widget>(this);  // BAD: double delete
    }
};

auto w = std::make_shared<Widget>();
auto w2 = w->getPtr();  // shares ownership, refcount = 2
14

ムーブセマンティクスと右辺値参照

左辺値、右辺値、参照

左辺値はアイデンティティを持ち、単一の式を超えて存続します(名前付きオブジェクト)。右辺値は一時的またはリテラル値です。T& は左辺値にバインド、T&& は右辺値にバインドします。const T& は特別 — 両方にバインドします。この区別の理解がムーブセマンティクスの基盤です。

cpp
int x = 10;          // x is an lvalue
int& lref = x;       // lvalue reference
int&& rref = 20;     // rvalue reference (binds to temporary)
// int& bad = 20;    // ERROR: can't bind lref to rvalue
const int& cref = 20;  // OK: const lref binds to rvalue

int y = x + 5;       // (x + 5) is an rvalue (prvalue)
std::string s1 = "hi";
std::string&& rr = std::move(s1);  // rr is named rvalue

std::move とムーブコンストラクタ

std::move は何もムーブしません — 右辺値にキャストし、ムーブコンストラクタ/代入が選択されるようにします。ムーブ操作は noexcept にすべきで、コンテナが再割り当て時に使用できるようにします(そうでない場合例外安全性のためにコピーにフォールバック)。ムーブ後、ソースオブジェクトは有効だが未指定の状態にあります。

cpp
class Buffer {
    int* data;
    size_t size;
public:
    // Move constructor: steal resources
    Buffer(Buffer&& other) noexcept
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    // Move assignment
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data; size = other.size;
            other.data = nullptr; other.size = 0;
        }
        return *this;
    }
};

Buffer a(100);
Buffer b = std::move(a);  // calls move ctor, a is now empty

完全転送

完全転送は値カテゴリ(左辺値 vs 右辺値)を保持しながら引数を別の関数に渡します。推論コンテキストの T&& は「転送参照」です(右辺値参照ではありません)。std::forward<T> は条件付きでキャストします:T が T& の場合は左辺値、T が T&& の場合は右辺値。ファクトリ関数とラッパーに不可欠です。

cpp
#include <utility>
template <typename T, typename Arg>
auto make_unique(Arg&& arg) {
    return std::unique_ptr<T>(new T(std::forward<Arg>(arg)));
}

template <typename... Args>
void log(Args&&... args) {
    // std::forward preserves value category
    log_impl(std::forward<Args>(args)...);
}

std::string s = "hello";
log(s);              // Arg = std::string&  (lvalue)
log(std::string());  // Arg = std::string&& (rvalue)

5の法則 / ゼロの法則

5の法則:クラスがリソースを管理する場合、デストラクタ、コピーコンストラクタ、コピー代入、ムーブコンストラクタ、ムーブ代入を定義しなければなりません。ゼロの法則:RAII 型(vector、string、スマートポインタ)を構成して、コンパイラ生成の特殊メンバが正しくなるように優先します。これでバグの多い手動リソース管理を排除します。

cpp
// Rule of Five: if you define any of these, define all 5
class Resource {
    int* data;
public:
    Resource(size_t n) : data(new int[n]) {}
    ~Resource() { delete[] data; }
    Resource(const Resource& o);             // copy ctor
    Resource& operator=(const Resource& o);  // copy assign
    Resource(Resource&& o) noexcept;         // move ctor
    Resource& operator=(Resource&& o) noexcept;  // move assign
};

// Rule of Zero: prefer to use RAII types
class Better {
    std::vector<int> data;  // handles everything
public:
    Better() = default;  // compiler-generated funcs are correct
};

戻り値最適化(RVO/NRVO)

RVO/NRVO はコンパイラが呼び出し側のストレージに直接戻り値を構築し、コピー/ムーブを完全に回避できるようにします。C++17 は prvalue に対して RVO を必須にしました。return std::move(local) は絶対に書かないでください — NRVO を阻害し、(より遅い)ムーブを強制します。local を名前で返し、コンパイラに最適化させてください。

cpp
std::vector<int> makeVec() {
    std::vector<int> v;
    v.push_back(1); v.push_back(2);
    return v;  // NRVO: no copy, no move!
}

std::string makeStr() {
    return std::string("hello");  // RVO
}

// C++17 guarantees RVO (mandatory copy elision) for prvalues
std::vector<int> v = makeVec();  // constructed in place

// Don't std::move return values—it disables RVO!
std::vector<int> bad() {
    std::vector<int> v;
    return std::move(v);  // WORSE: forces move, blocks NRVO
}
15

並行性(thread、mutex、async)

std::thread の基礎

std::thread は新しい OS スレッドを起動します。スレッドオブジェクトが破棄される前に join()(待機)または detach()(独立実行)を呼び出さなければなりません。引数はデフォルトで値渡しされます — 参照には std::ref を、ムーブ専用型には std::move を使用します。明確な理由がない限り join を優先してください。

cpp
#include <thread>
#include <iostream>

void worker(int id) {
    std::cout << "Thread " << id << "\n";
}

int main() {
    std::thread t1(worker, 1);
    std::thread t2([]{ std::cout << "Lambda thread\n"; });

    t1.join();   // wait for t1 to finish
    t2.join();   // must join or detach before destruction
    // t1.detach();  // runs independently (risky)

    // Hardware concurrency hint
    unsigned n = std::thread::hardware_concurrency();
}

ミューテックスと lock guard

共有データは常にミューテックスで保護してください。std::lock_guard は最もシンプルな RAII ロックです — 構築時に取得し、破棄時に解放します。std::scoped_lock(C++17)はデッドロック回避アルゴリズムで複数のミューテックスを安全にロックします。std::unique_lock は条件変数と使用するためのより柔軟性を提供します(手動ロック/アンロック、遅延ロック)。

cpp
#include <mutex>
#include <vector>

std::mutex mtx;
std::vector<int> shared;

void safePush(int val) {
    // RAII lock: unlocks even if exception thrown
    std::lock_guard<std::mutex> lock(mtx);
    shared.push_back(val);
}  // lock released here

// std::scoped_lock (C++17) locks multiple mutexes deadlock-free
std::mutex m1, m2;
void transfer() {
    std::scoped_lock lock(m1, m2);  // atomic
}

// std::unique_lock: lockable/unlockable, movable
std::unique_lock<std::mutex> ul(mtx);
ul.unlock();
ul.lock();

条件変数

condition_variable はスレッドが条件を待つことを可能にします。スプリアスウェイクアップを処理するため wait() に常に述語を使用してください。wait() 呼び出し時はミューテックスが unique_lock で保持されている必要があり、待機中に解放され、戻り前に再取得されます。notify_one は1つの待機者を起こし、notify_all はすべてを起こします。このパターンはスレッドセーフなキューとプロデューサ-コンシューマパイプラインを実装します。

cpp
#include <condition_variable>
#include <queue>
#include <thread>

std::mutex mtx;
std::condition_variable cv;
std::queue<int> tasks;

void producer() {
    {
        std::lock_guard<std::mutex> lock(mtx);
        tasks.push(42);
    }
    cv.notify_one();  // wake one waiting consumer
}

void consumer() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return !tasks.empty(); });  // predicate prevents spurious wakeup
    int task = tasks.front();
    tasks.pop();
}

std::async と future

std::async はタスクを非同期に実行する高レベルの方法で、future を返します。std::launch::async は新しいスレッドを強制;std::launch::deferred は get() で遅延実行します。デフォルトポリシーはどちらかを選ぶ可能性があるため — 予測可能な動作のために明示的にしてください。より制御するには std::promise/future ペアを使用します。破棄前に常に future で get() を呼び出してください、そうしないとデストラクタがブロックする可能性があります。

cpp
#include <future>
#include <iostream>

int slowComputation() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
}

// async policy: may run in new thread or deferred
auto fut = std::async(std::launch::async, slowComputation);
// do other work...
int result = fut.get();  // blocks until ready

// std::launch::async: definitely new thread
// std::launch::deferred: lazy, runs on get()
// std::launch::async | deferred: implementation chooses

// std::promise for manual control
std::promise<int> p;
std::future<int> f = p.get_future();
std::thread([&p]{ p.set_value(10); }).detach();
f.get();  // 10

アトミック操作

std::atomic はプリミティブ型のロックフリーなスレッドセーフ操作を提供します。plain int より重いですが、シンプルなカウンタ/フラグにはミューテックスよりはるかに軽いです。メモリ順序付けが可視性に影響:relaxed(順序なし)、acquire/release(同期のペア)、seq_cst(デフォルト、最強)。カウンタ/フラグにはアトミックを;複雑なクリティカルセクションにはミューテックスを使用します。

cpp
#include <atomic>
#include <iostream>

std::atomic<int> counter{0};
std::atomic<bool> ready{false};

void worker() {
    while (!ready.load(std::memory_order_acquire));
    counter.fetch_add(1, std::memory_order_relaxed);
}

// Compare-and-swap (CAS) loop
std::atomic<int> val{0};
int expected = 0;
bool success = val.compare_exchange_weak(
    expected, 1,
    std::memory_order_acq_rel);

// Atomic is lock-free for most primitive types
static_assert(std::atomic<int>::is_always_lock_free);

// std::atomic_flag: guaranteed lock-free, for spinlocks
std::atomic_flag spin = ATOMIC_FLAG_INIT;
while (spin.test_and_set(std::memory_order_acquire));  // acquire
spin.clear(std::memory_order_release);  // release
16

テンプレートメタプログラミング

テンプレートの特殊化

テンプレートの特殊化は特定の型にカスタム実装を提供します。完全特殊化はすべてのテンプレートパラメータを固定します。部分特殊化(クラステンプレートのみ)は一部のパラメータを特殊化し、他をジェネリックのままにします。型特性、std::vector<bool>、既知の型の最適化で多用されます。

cpp
// Primary template
template <typename T>
struct TypeName {
    static const char* get() { return "unknown"; }
};

// Full specialization for int
template <>
struct TypeName<int> {
    static const char* get() { return "int"; }
};

// Full specialization for const char*
template <>
struct TypeName<const char*> {
    static const char* get() { return "string"; }
};

std::cout << TypeName<int>::get();           // "int"
std::cout << TypeName<double>::get();        // "unknown"
std::cout << TypeName<const char*>::get();   // "string"

SFINAE と enable_if

SFINAE(Substitution Failure Is Not An Error)は型プロパティに基づいてテンプレートオーバーロードを有効/無効にします。std::enable_if は条件付きで型を定義します。置換が失敗した場合、オーバーロードはエラーを引き起こすのではなくサイレントに削除されます。C++17 の if constexpr と C++20 のコンセプトがよりクリーンな構文で SFINAE を置き換えることがよくあります。

cpp
#include <type_traits>

// SFINAE: Substitution Failure Is Not An Error
// Only enable this overload if T is integral
template <typename T,
          typename = std::enable_if_t<std::is_integral_v<T>>>
void process(T x) { std::cout << "integral\n"; }

// Only enable if T is floating point
template <typename T,
          std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
void process(T x) { std::cout << "floating\n"; }

process(10);    // integral
process(3.14);  // floating

// void_t trick (C++17) for detecting member existence
template <typename T, typename = void>
struct has_size : std::false_type {};
template <typename T>
struct has_size<T, std::void_t<decltype(std::declval<T>().size())>>
    : std::true_type {};

if constexpr(C++17)

if constexpr はコンパイル時に条件を評価し、false ブランチを完全に破棄します(型チェックすらされません)。これが多くの SFINAE パターンをよりクリーンなコードで置き換えます。テンプレート再帰(ベースケースが再帰を終了)と、インスタンス化エラーなしの型特性分岐に特に便利です。

cpp
#include <type_traits>
template <typename T>
auto getValue(T t) {
    if constexpr (std::is_pointer_v<T>) {
        return *t;  // only compiled if T is a pointer
    } else if constexpr (std::is_integral_v<T>) {
        return t * 2;
    } else {
        return t;
    }
}

int x = 5;
int* p = &x;
getValue(x);   // 10
getValue(p);   // 5
getValue(3.14);  // 3.14

// Compile-time recursion with termination
template <int N>
constexpr int factorial() {
    if constexpr (N <= 1) return 1;
    else return N * factorial<N - 1>();
}

可変長テンプレートとフォールド式

可変長テンプレートはパラメータパック(typename... Args)経由で任意の数の引数を受け取ります。C++17 のフォールド式はすべてのパック要素に演算子を適用します:単項フォールド(... op pack)、二項フォールド(init op ... op pack)。C++17 以前はベースケース付きの再帰が必要でした。可変長テンプレートは std::make_unique、std::tuple、printf 風関数を支えます。

cpp
#include <iostream>

// Parameter pack
template <typename... Args>
void print(Args... args) {
    // C++17 fold expression
    ((std::cout << args << " "), ...);
    std::cout << "\n";
}

print(1, "hello", 3.14, 'x');  // 1 hello 3.14 x

// Sum with fold
template <typename... T>
auto sum(T... args) {
    return (args + ...);  // binary fold: ((a+b)+c)+d
}

// Sum with initial value
template <typename... T>
auto sumFrom0(T... args) {
    return (0 + ... + args);  // left fold with init
}

// Base case recursion (pre-C++17)
template <typename T>
void printOne(T t) { std::cout << t; }

コンセプト(C++20)

コンセプト(C++20)は SFINAE を読みやすく意図を明らかにする制約で置き換えます。SFINAE よりはるかに良いエラーメッセージを生成します。concept Name = constraint; でコンセプトを定義します。テンプレートパラメータ、requires 節、または省略テンプレート(コンセプト付き auto)で使用します。標準ライブラリは <concepts> に多くの有用なコンセプトを提供します。

cpp
#include <concepts>

// Define a concept
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

// Use as constraint
template <Numeric T>
T add(T a, T b) { return a + b; }

// Requires clause
template <typename T>
requires requires(T t) { t.size(); }
auto getSize(const T& t) { return t.size(); }

// Concepts in abbreviated function templates
void process(std::integral auto x) { /* ... */ }
void process(std::ranges::range auto& r) { /* ... */ }

// Standard concepts: integral, floating_point, equality_comparable,
// totally_ordered, copyable, movable, default_initializable, etc.
17

ラムダ式の深掘り

キャプチャモード

ラムダは囲むスコープから変数をキャプチャします。[=] はすべてを値で、[&] は参照でキャプチャ — 便利ですがエラーを起こしやすいです(ダングリング参照、意図しないキャプチャ)。明確さのために明示的キャプチャ [x、&y] を優先してください。初期化キャプチャ [name = expr](C++14)はリネーム、ムーブ、キャプチャ値の計算を可能にします。[&] は慎重にキャプチャ — ラムダがスコープより長生きするとダングリング参照を引き起こします。

cpp
int x = 10, y = 20;
std::vector<int> v;

auto a = [] { /* no capture */ };
auto b = [x] { return x; };           // capture x by value
auto c = [&x] { x = 100; };           // capture x by reference
auto d = [=] { return x + y; };       // capture all by value
auto e = [&] { x = 1; y = 2; };       // capture all by reference
auto f = [x, &y] { return x + y; };   // mixed
auto g = [=, &x] { x = 1; return y; }; // default value, x by ref
auto h = [this] { return member; };   // capture this (C++17: *this)
auto i = [x = x + 5] { return x; };   // init capture (C++14)

// Best practice: capture only what you need, explicitly

ジェネリックラムダ(C++14)

ジェネリックラムダは auto パラメータ(C++14)または明示的テンプレートパラメータ(C++20)を使用します。これらは本質的にコンパイラ生成のテンプレート operator() オーバーロードです。C++20 のテンプレートラムダは型パラメータ T に直接アクセスできます。再帰ラムダは std::function(または C++23 の推論 this)が必要です — 型が知られる前に名前で自身を参照できないためです。

cpp
// auto parameters (C++14)
auto add = [](auto a, auto b) { return a + b; };
add(1, 2);        // int
add(1.5, 2.5);    // double
add(std::string("a"), std::string("b"));

// Template lambdas (C++20)
auto getSize = []<typename T>(const std::vector<T>& v) {
    return v.size();
};

// Perfect forwarding in lambdas (C++20)
auto wrapper = []<typename T>(T&& arg) {
    return process(std::forward<T>(arg));
};

// Recursive lambda with std::function
#include <functional>
std::function<int(int)> fib = [&](int n) {
    return n < 2 ? n : fib(n - 1) + fib(n - 2);
};

mutable と状態付きラムダ

デフォルトでは、ラムダの値キャプチャ変数は const です — call operator() は const です。mutable キーワードはこの const 性を削除し、値キャプチャ変数の変更を許可します(元ではなくコピーを変更)。これでカウンタのような状態付きラムダが可能になります。ラムダの各コピーは独自のキャプチャ状態を持ちます。異なる型のラムダをコンテナに格納するには std::function を使用します。

cpp
#include <functional>
int counter = 0;

// Without mutable, value-captured vars are const
auto inc = [counter]() mutable {
    return ++counter;  // modifies the captured copy
};
inc(); inc();
std::cout << counter;  // still 0 (original unchanged)

// Stateful counter with std::function
auto makeCounter(int start) {
    return [count = start]() mutable { return ++count; };
}
auto c = makeCounter(10);
c(); c();  // 11, 12

// Lambda as callback storage
std::vector<std::function<void()>> callbacks;
callbacks.push_back([&]{ std::cout << "click"; });

STL アルゴリズムのラムダ

ラムダは STL アルゴリズムの述語/コンパレータとして輝きます。古いファンクタ(関数オブジェクト)アプローチをインラインで読みやすいコードで置き換えます。一般的な用途:sort のコンパレータ、find_if/remove_if/copy_if の述語、transform の変換。C++20 の ranges はパイプ演算子で合成可能な変換を可能にし、さらにクリーンになります。

cpp
#include <algorithm>
#include <vector>

std::vector<int> v = {5, 2, 8, 1, 9, 3};

// Sort descending
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;
});

// Find first even
auto it = std::find_if(v.begin(), v.end(),
    [](int x) { return x % 2 == 0; });

// Remove odds (erase-remove idiom)
v.erase(std::remove_if(v.begin(), v.end(),
    [](int x) { return x % 2 != 0; }), v.end());

// Transform
std::transform(v.begin(), v.end(), v.begin(),
    [](int x) { return x * x; });

// C++20 ranges: cleaner
auto evens = v | std::views::filter([](int x){ return x%2==0; })
               | std::views::transform([](int x){ return x*2; });

高階関数

ラムダは C++ で関数型プログラミングパターンを可能にします。関数はラムダ(クロージャ)を返したり、パラメータとして受け取ったりできます。std::function は型消去ストレージのために任意の callable をラップします。関数合成はラムダをチェーンしてパイプラインを構築します。このスタイルはコールバック、イベントハンドラ、データ変換パイプラインに強力ですが、テンプレートベースのアプローチは std::function のオーバーヘッドを回避します。

cpp
#include <functional>
// Function returning a function
auto makeMultiplier(int factor) {
    return [factor](int x) { return x * factor; };
}
auto doubleIt = makeMultiplier(2);
auto tripleIt = makeMultiplier(3);
doubleIt(5);  // 10
tripleIt(5);  // 15

// Function taking a function
template <typename F>
void applyTwice(int x, F f) {
    std::cout << f(f(x));
}
applyTwice(5, [](int x){ return x + 3; });  // 11

// Composition
auto compose = [](auto f, auto g) {
    return [f, g](auto x) { return f(g(x)); };
};
auto addOne = [](int x){ return x + 1; };
auto square = [](int x){ return x * x; };
auto sqThenAdd = compose(addOne, square);
sqThenAdd(3);  // 10
18

名前空間とモジュール

名前空間の基礎

名前空間は大きなプロジェクトでの名前衝突を防ぎます。ネストとエイリアスが可能です。匿名名前空間はシンボルに内部リンケージを与え(現在の翻訳単位のみで可視)、この目的で C スタイルの static キーワードを置き換えます。ヘッダで using namespace は避けてください — すべてのインクルードファイルを汚染します。

cpp
namespace math {
    double pi = 3.14159;
    double square(double x) { return x * x; }

    namespace geometry {  // nested
        double circleArea(double r) { return pi * square(r); }
    }
}

// Usage
double a = math::square(5);
double area = math::geometry::circleArea(2.0);

// Namespace alias
namespace geo = math::geometry;
geo::circleArea(1.0);

// Anonymous namespace: internal linkage (like 'static')
namespace {
    int internalVar = 42;  // only visible in this file
}

using 宣言とディレクティブ

using 宣言(using std::cout)は単一の名前をインポート — 安全で推奨されます。using ディレクティブ(using namespace std)はすべてをインポート — 便利ですがリスキ、特にヘッダでは(名前衝突と曖昧さを引き起こす)。using ディレクティブは小さなスコープ(関数/実装ファイル)に留め、ヘッダでは絶対に使用しないでください。C++20 の using enum はスコープ付き enum のすべての列挙子をインポートします。

cpp
#include <iostream>
#include <vector>

// using declaration: brings one name
using std::cout;
using std::vector;
cout << "hello";
vector<int> v;

// using directive: brings entire namespace
using namespace std;  // brings ALL of std
// AVOID in headers! Pollutes global namespace.

// Namespace-scoped using (safe)
namespace mylib {
    using std::string;  // only affects mylib
    using std::vector;
    string s;
}

// C++20: using enum
enum class Color { Red, Green, Blue };
void print() {
    using enum Color;
    auto c = Red;  // no Color:: needed here
}

引数依存ルックアップ(ADL)

ADL(Koenig ルックアップ)は引数の名前空間に基づいて自由関数を見つけます。これが std::operator<< なしで std::cout << x が動作する理由 — コンパイラが x の名前空間を探します。ADL は演算子オーバーロードと swap のようなカスタマイゼーションポイントに不可欠です。'using std::swap; swap(a,b);' パターンはユーザー型が最適化された swap を提供しつつ std::swap にフォールバックできるようにします。

cpp
namespace mylib {
    struct Widget {
        int value;
    };
    // Operator overload found via ADL
    std::ostream& operator<<(std::ostream& os, const Widget& w) {
        return os << "Widget(" << w.value << ")";
    }

    void helper(const Widget&) {}
}

int main() {
    mylib::Widget w{42};
    // ADL: finds operator<< in mylib without qualification
    std::cout << w;  // works! no mylib:: needed

    // ADL applies to free functions too
    helper(w);  // wait—needs mylib::helper unless ADL applies
    mylib::helper(w);  // explicit
}

// swap is the classic ADL use case
namespace ns { struct X {};
    void swap(X&, X&) {}  // customized swap
}
void f(ns::X& a, ns::X& b) {
    using std::swap;
    swap(a, b);  // ADL picks ns::swap if available
}

インライン名前空間(バージョニング)

インライン名前空間はメンバを囲む名前空間にあるように公開します。これでライブラリのバージョニングが可能です:最新版をインラインにしてユーザーがデフォルトで取得するようにし、古いバージョンは明示的修飾でアクセス可能にします。ABI 互換性と機能トグルにも使用されます。インラインにする名前空間を変更すると、ユーザーコードを変更せずにデフォルトバージョンを切り替えられます。

cpp
// Inline namespace members are part of the enclosing namespace
namespace mylib {
    inline namespace v2 {
        struct Widget { int x, y, z; };  // newer version
        void process(Widget) {}
    }

    namespace v1 {
        struct Widget { int x, y; };  // older version
    }
}

// v2::Widget is accessible as mylib::Widget (inline)
mylib::Widget w;  // actually mylib::v2::Widget
mylib::process(w);

// Explicitly use v1 if needed
mylib::v1::Widget oldW;

// ABI versioning: change which namespace is inline
// to change default version without breaking old code

C++20 モジュール

C++20 モジュールは #include をより高速で堅牢なシステムで置き換えます。export module でモジュールを宣言;export で可視宣言をマークします。モジュールは一度コンパイルされ(翻訳単位ごとに再解析されない)、ビルド時間を劇的に改善します。マクロ汚染とヘッダ順序の問題を回避します。採用は段階的 — ツールチェーンサポート(CMake、ビルドシステム)は2024年時点でまだ成熟中です。

cpp
// math.cppm (module interface unit)
export module math;

export double pi = 3.14159;

export double square(double x) {
    return x * x;
}

// Internal (not exported)
double internalHelper(double x) {
    return x * 2;
}

// main.cpp
import math;
import std;  // standard library module (C++23)

int main() {
    return square(pi);  // 9.87
    // internalHelper(3);  // ERROR: not exported
}

// Module partitions (sub-modules)
export module math:geometry;
export double circleArea(double r);
19

プリプロセッサとマクロ

インクルードガードと #pragma once

インクルードガードは1つの翻訳単位でヘッダが複数回処理されるのを防ぎ、再定義エラーを回避します。#ifndef/#define/#endif が標準でポータブルです。#pragma once はよりシンプルでマクロ名衝突を回避しますが、技術的には非標準です(すべての主要コンパイラでサポート)。モダンコードは簡素さのために #pragma once をよく使用します。

cpp
// Traditional include guard (header.h)
#ifndef MY_HEADER_H
#define MY_HEADER_H

// declarations here
class Widget { /* ... */ };

#endif  // MY_HEADER_H

// Alternative: #pragma once (non-standard but widely supported)
#pragma once

class Widget { /* ... */ };

// #pragma once pros: simpler, no risk of macro name collision
// Include guards pros: standard, works everywhere
// Both prevent multiple inclusion in a single TU

関数のようなマクロ

関数のようなマクロはテキスト置換です。優先順位バグを避けるため、各引数と式全体を常に括弧で囲んでください。do { ... } while (0) イディオムはマクロを単一文のように動作させます。マクロには型チェックがなく、スコープがなく、副作用がある可能性があります(MAX(i++、j++) は2回インクリメント)。モダン C++ では constexpr/inline/テンプレートを優先してください。

cpp
// Basic macro
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))

int m = MAX(3, 5);      // ((3) > (5) ? (3) : (5))
int s = SQUARE(4);      // ((4) * (4))

// ALWAYS parenthesize arguments!
#define BAD(x) x * x
BAD(1 + 2);  // 1 + 2 * 1 + 2 = 5, not 9!

// Multi-line macro with do-while
#define LOG(msg) do { \
    std::cerr << __FILE__ << ":" << __LINE__ \
              << " " << msg << "\n"; \
} while (0)

// Variadic macro (C++11)
#define PRINT(...) printf(__VA_ARGS__)
PRINT("x=%d, y=%d\n", x, y);

文字列化と連結

#(文字列化)はマクロ引数を文字列リテラルに変換します。##(トークン貼り付け)は2つのトークンを1つに連結します。2レベルの STR/XSTR トリックは最初にマクロを展開してから文字列化します。これらの演算子はコード生成、ロギングマクロ、X-macro に不可欠です。モダン C++ はこれらを constexpr 関数で置き換えることがよくありますが、コンパイル時の文字列/トークン生成に依然として有用です。

cpp
// # (stringification) turns a macro argument into a string literal
#define STR(x) #x
#define XSTR(x) STR(x)
const char* s1 = STR(hello world);  // "hello world"
const char* s2 = STR(42);           // "42"

// Two-level macro to expand macros before stringifying
#define VERSION 100
const char* v1 = STR(VERSION);   // "VERSION"
const char* v2 = XSTR(VERSION);  // "100"

// ## (token pasting) concatenates tokens
#define CONCAT(a, b) a ## b
#define MAKE_VAR(n) var_ ## n
int MAKE_VAR(1) = 10;  // int var_1 = 10;
int CONCAT(foo, bar) = 5;  // int foobar = 5;

// Useful for generating unique names
#define UNIQUE(prefix) prefix ## __LINE__
int UNIQUE(tmp_) = 0;  // int tmp_42 = 0; (if on line 42)

条件付きコンパイル

条件付きコンパイル(#if、#ifdef、#ifndef、#elif、#else、#endif)はコンパイル時にコードを含める/除外します。プラットフォーム固有コード、デバッグビルド、フィーチャーフラグに使用されます。defined(X) は X が定義されているかチェック(値は無関係)。#error はメッセージ付きでコンパイルを中止します。可能な場合は constexpr if を優先してください — 型安全でコードは常にコンパイルされます(すべての設定でエラーをキャッチ)。

cpp
#define DEBUG 1
#define PLATFORM "windows"

#if DEBUG
    std::cout << "Debug mode\n";
#endif

#ifdef DEBUG
    logDebug("entered function");
#endif

#ifndef NDEBUG
    assert(ptr != nullptr);
#endif

#if defined(_WIN32) || defined(_WIN64)
    #include <windows.h>
#elif defined(__linux__)
    #include <unistd.h>
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
#endif

// Check C++ standard
#if __cplusplus >= 202002L
    #define CPP20_OR_LATER
#elif __cplusplus >= 201703L
    #define CPP17_OR_LATER
#endif

// #error and #warning
#if !defined(VERSION)
    #error "VERSION must be defined"
#endif

定義済みマクロと __VA_OPT__

定義済みマクロはコンパイル時情報を提供します:ロギング用 __FILE__/__LINE__、標準バージョン用 __cplusplus、関数名用 __func__(マクロではなく識別子)。__VA_OPT__(C++20)は __VA_ARGS__ が空でない場合にのみカンマを条件付きで挿入し、可変長マクロの末尾カンマ問題を修正します。__has_include(C++17)はポータビリティのためのオプションヘッダインクルードを可能にします。

cpp
// Standard predefined macros
std::cout << __FILE__;       // source file path
std::cout << __LINE__;       // current line number
std::cout << __DATE__;       // compilation date "Mmm dd yyyy"
std::cout << __TIME__;       // compilation time "hh:mm:ss"
std::cout << __cplusplus;    // C++ standard version
std::cout << __func__;       // current function name (not macro)

// Compiler-specific
#ifdef __GNUC__
    std::cout << __GNUC__ << "." << __GNUC_MINOR__;
#endif
#ifdef _MSC_VER
    std::cout << "MSVC " << _MSC_VER;
#endif

// __VA_OPT__ (C++20): expands to its arg if variadic has args
#define LOG(fmt, ...) \
    printf(fmt __VA_OPT__(,) __VA_ARGS__)
LOG("plain");              // printf("plain")
LOG("x=%d", x);            // printf("x=%d", x)

// __has_include (C++17): check if header exists
#if __has_include(<optional>)
    #include <optional>
#endif
20

C++ でのデザインパターン

Singleton(Meyers の Singleton)

Singleton はクラスがグローバルアクセスで1つのインスタンスを持つことを保証します。Meyers の Singleton(static ローカル変数)が最もクリーンな C++ 実装です — C++11 以降スレッドセーフな初期化が保証されます。複製を防ぐためコピー操作を削除します。Singleton は議論の的です(グローバル状態、テスト困難);実現可能な場合は依存性の注入を優先してください。真に1つのインスタンスが必要な場合(ロガー、設定、ハードウェアインターフェース)に使用します。

cpp
class Logger {
public:
    static Logger& instance() {
        // Meyers' singleton: thread-safe in C++11+
        static Logger inst;
        return inst;
    }
    void log(const std::string& msg) {
        std::cout << "[LOG] " << msg << "\n";
    }
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
private:
    Logger() = default;
};

// Usage
Logger::instance().log("started");

// Avoid: lazy init with new + manual sync (error-prone)
// The static local is initialized once, thread-safely

RAII(Resource Acquisition Is Initialization)

RAII は C++ の最も重要なイディオムです:コンストラクタでリソースを取得し、デストラクタで解放します。これにより例外がスローされてもクリーンアップが保証されます。スマートポインタ、std::lock_guard、std::fstream、std::vector はすべて RAII を使用します。デストラクタはスタック巻き戻し中に実行され、例外安全なコードを自然にします。RAII は手動の new/delete、lock/unlock、open/close を排除します — どこでも活用してください。

cpp
// RAII: resource tied to object lifetime
class FileHandle {
    FILE* fp;
public:
    explicit FileHandle(const char* path) : fp(fopen(path, "r")) {
        if (!fp) throw std::runtime_error("open failed");
    }
    ~FileHandle() { if (fp) fclose(fp); }
    FILE* get() { return fp; }
    // disable copy to prevent double-close
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;
    // allow move
    FileHandle(FileHandle&& o) noexcept : fp(o.fp) { o.fp = nullptr; }
};

{
    FileHandle f("data.txt");
    // use f.get()
}  // fclose called automatically, even on exception

Pimpl イディオム(Pointer to Implementation)

Pimpl(Pointer to Implementation)は実装の詳細をポインタの背後に隠します。利点:より速いコンパイル(プライベートメンバがヘッダに現れない)、真の ABI 安定性(Impl の変更が ABI を壊さない)、削減されたインクルード依存。unique_ptr<Impl> が完全な型を必要とするため、デストラクタは .cpp ファイルで定義しなければなりません。安定した API のライブラリデザインで一般的です。

cpp
// widget.h - public interface
class Widget {
public:
    Widget();
    ~Widget();  // must define in .cpp (unique_ptr needs complete type)
    Widget(Widget&&) noexcept;
    Widget& operator=(Widget&&) noexcept;
    void doSomething();
private:
    class Impl;  // forward declaration
    std::unique_ptr<Impl> pimpl;
};

// widget.cpp - implementation
#include "widget.h"
#include <vector>
class Widget::Impl {
public:
    std::vector<int> data;
    void doSomething() { /* ... */ }
};
Widget::Widget() : pimpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
Widget::Widget(Widget&&) noexcept = default;
void Widget::doSomething() { pimpl->doSomething(); }

Observer パターン

Observer はサブジェクトが結合せずにサブスクライバに変更を通知することを可能にします。std::function でラムダ、メンバ関数、ファンクタを受け取りやすくなります。本番コードでは、トークンベースのサブスクライブ解除メカニズムを検討してください(サブスクライブ時に ID を返し、それを使用して削除)。オブザーバが [&] をキャプチャしサブジェクトより長生きする場合のダングリング参照に注意してください。Qt の signals/slots と C# のイベントが成熟した実装です。

cpp
#include <functional>
#include <vector>
#include <string>

class Subject {
    std::vector<std::function<void(const std::string&)>> observers;
public:
    void subscribe(std::function<void(const std::string&)> cb) {
        observers.push_back(cb);
    }
    void notify(const std::string& event) {
        for (auto& cb : observers) cb(event);
    }
};

// Usage
Subject s;
s.subscribe([](const std::string& e){ std::cout << "A: " << e; });
s.subscribe([](const std::string& e){ std::cout << "B: " << e; });
s.notify("hello");  // both callbacks invoked

// For removal, use a token/ID system or observer objects
// instead of std::function

CRTP(Curiously Recurring Template Pattern)

CRTP(Derived : Base<Derived>)は静的ポリモーフィズムを達成します — ベースクラスが派生型経由で振る舞いをカスタマイズします。仮想関数オーバーヘッドなし。mixin 経由で派生クラスに機能(Comparable、Iterable)を追加するために使用されます。テンプレートが派生型を保証するため static_cast 経由のダウンキャストは安全です。CRTP は std::enable_shared_from_this、std::iterator、多くのポリシベース設計を支えます。

cpp
// CRTP: class Derived : public Base<Derived>
template <typename Derived>
struct Comparable {
    bool operator==(const Derived& other) const {
        return static_cast<const Derived*>(this)->equalTo(other);
    }
    bool operator!=(const Derived& other) const {
        return !(*this == other);
    }
};

struct Point : Comparable<Point> {
    int x, y;
    bool equalTo(const Point& o) const { return x == o.x && y == o.y; }
};

Point a{1, 2}, b{1, 2};
a == b;  // true (uses Comparable::operator==)
a != b;  // false

// Static polymorphism (no virtual overhead)
template <typename T>
void draw(const T& shape) {
    static_cast<const T&>(shape).drawImpl();
}
21

スマートポインタの深掘り

unique_ptr

unique_ptr はオブジェクトの単独所有者です。コピーできず、ムーブのみ可能。スコープ外で自動削除。make_unique が推奨される作成方法です。生ポインタに対してゼロオーバーヘッド。

cpp
#include <memory>
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1;  // Error: cannot copy
std::unique_ptr<int> p3 = std::move(p1);  // OK: transfer ownership
// p1 is now nullptr

shared_ptr

shared_ptr は参照カウントで複数の所有者を許可します。use_count() が所有者数を表示。カウンタはスレッドセーフですがオブジェクトはそうではありません。効率のために make_shared を使用します(単一割り当て)。

cpp
auto p1 = std::make_shared<int>(42);
auto p2 = p1;  // OK: shared ownership
std::cout << p1.use_count();  // 2
// Reference counting: deleted when count reaches 0
// Thread-safe for reference count, not for the object

weak_ptr

weak_ptr は shared_ptr の非所有参照です。循環参照(メモリリーク)を防ぎます。lock() で shared_ptr への変換を試みます。expired() でオブジェクトがまだ存在するかチェック。参照カウントに影響しません。

cpp
auto shared = std::make_shared<int>(42);
std::weak_ptr<int> weak = shared;  // Does not increase count
if (auto locked = weak.lock()) {  // Try to get shared_ptr
    std::cout << *locked;  // Use the object
} else {
    std::cout << "Object deleted";
}

カスタムデリータ

カスタムデリータは unique_ptr にファイルハンドル、ソケット、C API のような非メモリリソースの管理を許可します。デリータ型は unique_ptr 型の一部です。C ライブラリとの RAII に便利です。

cpp
std::unique_ptr<FILE, decltype(&fclose)> file(fopen("test.txt", "r"), fclose);
// Or with lambda
auto deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(deleter)> file2(fopen("test.txt", "r"), deleter);

enable_shared_from_this

enable_shared_from_this はオブジェクトが自身への shared_ptr を安全に取得することを許可します。shared_ptr(this) を直接呼び出すと2番目の参照カウントが作成され、二重削除を引き起こします。enable_shared_from_this から継承し shared_from_this() を使用してください。

cpp
class Node : public std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getPtr() {
        return shared_from_this();  // Safe
        // return std::shared_ptr<Node>(this);  // BUG: double delete
    }
};
22

ムーブセマンティクスの深掘り

ムーブコンストラクタ

ムーブコンストラクタはコピーの代わりにリソースを盗みます。noexcept が重要です:STL コンテナはムーブが noexcept の場合のみムーブします(コピーしない)。ムーブ元オブジェクトは常に有効な状態に残してください。

cpp
class Buffer {
    int* data; size_t size;
public:
    Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr; other.size = 0;  // Leave in valid state
    }
    Buffer& operator=(Buffer&& other) noexcept {
        delete[] data;
        data = other.data; size = other.size;
        other.data = nullptr; other.size = 0;
        return *this;
    }
};

std::move

std::move は右辺値参照へのキャストです。ムーブ自体を実行しません。ムーブコンストラクタまたは代入演算子が実際の作業を行います。std::move 後、ソースオブジェクトは有効だが未指定の状態にあります。

cpp
std::string s1 = "Hello";
std::string s2 = std::move(s1);  // s1 is now empty
// std::move does not move anything!
// It casts to an rvalue reference, enabling move
// The actual move happens in the constructor/assignment

完全転送

完全転送は引数の値カテゴリを保持します。テンプレートの T&& は転送参照で、左辺値と右辺値の両方にバインドします。std::forward<T> が元のカテゴリにキャストし戻します。ファクトリ関数とラッパーに不可欠です。

cpp
template<typename T, typename Arg>
auto make(Arg&& arg) {
    return std::make_shared<T>(std::forward<Arg>(arg));
}
// std::forward preserves value category:
// lvalue stays lvalue, rvalue stays rvalue
// Arg&& is a forwarding reference (not rvalue reference)

RVO と NRVO

RVO(Return Value Optimization)と NRVO(Named RVO)はオブジェクトをインプレース構築してコピーを排除します。コンパイラが自動的に実行します。ローカル戻り値で std::move を使用すると NRVO を阻害し、悪化する可能性があります。コンパイラを信頼してください。

cpp
std::string create() {
    std::string s = "Hello";
    return s;  // NRVO: no copy, no move
}
std::string s = create();  // RVO: no copy
// With -O2, compilers eliminate the copy/move entirely
// Do NOT use std::move on return of local variable!

5の法則

5の法則:デストラクタ、コピーコンストラクタ/代入、ムーブコンストラクタ/代入のいずれかを定義する場合、すべての5つを定義してください。これで正しいリソース管理を保証します。ゼロの法則が推奨されます:RAII 型(スマートポインタ、vector)を使用して手動管理を回避します。

cpp
class Resource {
public:
    Resource();  // Constructor
    ~Resource();  // Destructor
    Resource(const Resource&);  // Copy constructor
    Resource& operator=(const Resource&);  // Copy assignment
    Resource(Resource&&) noexcept;  // Move constructor
    Resource& operator=(Resource&&) noexcept;  // Move assignment
};
23

テンプレートの深掘り

可変長テンプレート

可変長テンプレートは任意の数の引数を受け取ります。フォールド式(C++17)がアンパックを簡素化します。再帰アプローチは C++11 で動作します。sizeof...(args) がカウントを与えます。tuple、make_shared、printf 置換で使用されます。

cpp
template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << '\n';  // C++17 fold expression
}
print(1, "hello", 3.14, 'a');  // 1hello3.14a
// Recursive unpacking (pre-C++17)
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first;
    print(rest...);
}

SFINAE

SFINAE(Substitution Failure Is Not An Error)は無効なオーバーロードを考慮から削除します。enable_if がテンプレートに条件を追加します。条件が false の場合、特殊化は無視されます。C++17 の if constexpr がしばしばよりクリーンです。

cpp
template<typename T,
         typename = std::enable_if_t<std::is_integral_v<T>>>
void process(T value) { /* integer version */ }
template<typename T,
         typename = std::enable_if_t<std::is_floating_point_v<T>>,
         typename = void>  // Different signature
void process(T value) { /* float version */ }

if constexpr

if constexpr(C++17)はコンパイル時に条件を評価します。true ブランチのみがコンパイルされ、他は破棄されます。条件付きコンパイルに SFINAE よりはるかにクリーンです。auto 戻り値型推論と動作します。

cpp
template<typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>)
        return *t;
    else
        return t;
}
// Only the matching branch is compiled
// No SFINAE needed

コンセプト(C++20)

コンセプト(C++20)は読みやすい構文でテンプレートパラメータを制約します。SFINAE より良いエラーメッセージを提供します。既存のコンセプト(integral、floating_point)を使用するかカスタムを定義します。コンセプトは && と || で組み合わせできます。

cpp
template<typename T>
concept Number = std::integral<T> || std::floating_point<T>;
template<Number T>
T add(T a, T b) { return a + b; }
// Or: requires clause
template<typename T> requires Number<T>
T multiply(T a, T b) { return a * b; }

テンプレートの特殊化

完全特殊化は特定の型に完全な実装を提供します。部分特殊化はカテゴリ(例:すべてのポインタ)にカスタマイズします。プライマリテンプレートが最初に宣言されなければなりません。特殊化はインターフェースにマッチしなければなりません。

cpp
template<typename T>
class Vector { /* general implementation */ };
template<>
class Vector<bool> {  // Full specialization
    // Bit-packed implementation
};
template<typename T>
class Vector<T*> {  // Partial specialization for pointers
    // Pointer-specific implementation
};
24

STL アルゴリズムの深掘り

sort と stable_sort

sort は O(n log n) で、安定ではありません。stable_sort は等しい要素の相対順序を保持します。カスタム順序付けにコンパレータを使用します。C++20 の ranges では:std::ranges::sort(v、{}、&last_digit) が可能です。

cpp
std::vector<int> v = {3, 1, 4, 1, 5, 9};
std::sort(v.begin(), v.end());  // 1,1,3,4,5,9
std::sort(v.begin(), v.end(), std::greater<>());  // Descending
// Custom comparator
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a % 10 < b % 10;  // Sort by last digit
});

transform と accumulate

transform は各要素に関数を適用します。accumulate は演算で要素をフォールドします(デフォルト +)。初期値が型を決定します。並列リダクションには std::reduce(C++17)を使用します。

cpp
std::vector<int> v = {1, 2, 3, 4};
std::vector<int> squared(v.size());
std::transform(v.begin(), v.end(), squared.begin(),
    [](int x) { return x * x; });  // 1,4,9,16
int sum = std::accumulate(v.begin(), v.end(), 0);  // 10
int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<>());

find と count

find は最初のマッチまたは end() へのイテレータを返します。count はマッチ数を返します。find_if は述語を使用します。すべて O(n) です。ソート済み範囲には binary_search、lower_bound、upper_bound(O(log n))を使用します。

cpp
std::vector<int> v = {1, 2, 3, 2, 1};
auto it = std::find(v.begin(), v.end(), 2);  // First 2
size_t cnt = std::count(v.begin(), v.end(), 2);  // 2
auto it2 = std::find_if(v.begin(), v.end(),
    [](int x) { return x > 2; });  // First > 2

copy と remove

copy_if はマッチする要素をコピーします。back_inserter が宛先に追加します。remove は実際には削除せず;マッチしない要素を前にシフトし新しい終端を返します。erase が残りを削除します。C++20 はコンテナに std::erase を追加します。

cpp
std::vector<int> v = {1, 2, 3, 4, 5};
std::vector<int> evens;
std::copy_if(v.begin(), v.end(), std::back_inserter(evens),
    [](int x) { return x % 2 == 0; });  // 2,4
// Remove-erase idiom
v.erase(std::remove(v.begin(), v.end(), 3), v.end());
// C++20: std::erase(v, 3);

minmax と clamp

minmax は1パスで (min、max) のペアを返します。clamp は値を範囲に制限します。min/max は初期化リストまたは2つの値を受け取ります。すべてカスタムコンパレータを受け入れます。構造化束縛(C++17)が結果を簡素化します。

cpp
auto [min, max] = std::minmax({3, 1, 4, 1, 5});  // C++17
int value = std::clamp(15, 0, 10);  // 10 (clamped to max)
int m = std::min({1, 2, 3});
int m2 = std::max({1, 2, 3}, [](int a, int b) { return a < b; });
25

並行性

std::thread

std::thread は OS スレッドを作成します。join() は完了を待機(ブロック)。detach() は独立実行(作成者より長生きする可能性)。どちらもないスレッドは std::terminate を引き起こします。引数は値渡しまたは参照に std::ref を使用します。

cpp
#include <thread>
void task(int n) { /* ... */ }
std::thread t1(task, 42);
std::thread t2([]() { /* lambda */ });
t1.join();  // Wait for completion
t2.detach();  // Run independently
// Always join or detach before destruction

std::mutex

lock_guard はシンプルな RAII です:構築時にロック、破棄時にアンロック。unique_lock はより柔軟:アンロック/再ロック可能、条件変数と使用。lock_guard では手動アンロックは絶対にしないでください。複数ミューテックスには std::scoped_lock を使用します。

cpp
std::mutex mtx;
int shared = 0;
void increment() {
    std::lock_guard<std::mutex> lock(mtx);  // RAII
    ++shared;
}  // Auto-unlock
// std::unique_lock for conditional locking
std::unique_lock<std::mutex> ulock(mtx);
ulock.unlock();  // Manual unlock
ulock.lock();    // Re-lock

std::async と future

std::async は関数を非同期実行し、future を返します。get() がブロックし結果を取得。launch::async は新しいスレッドを強制。launch::deferred は get() で同期的に実行。デフォルトはどちらかの可能性。例外は get() 経由で伝播します。

cpp
#include <future>
std::future<int> f = std::async(std::launch::async, []() {
    return 42;
});
int result = f.get();  // Blocks until ready
// std::launch::async: always new thread
// std::launch::deferred: lazy, runs on get()

条件変数

condition_variable はスレッドを同期します。wait はロックを解放し通知されるまでブロック。述語がスプリアスウェイクアップを処理。notify_one は1つの待機者を起こし、notify_all はすべてを起こします。通知前に常にミューテックス下で共有状態を変更してください。

cpp
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
// Waiter
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []() { return ready; });  // Spurious wakeups handled
// Notifier
{
    std::lock_guard<std::mutex> lk(mtx);
    ready = true;
}
cv.notify_one();  // Or notify_all()

アトミック操作

atomic はロックフリーなスレッドセーフ操作を提供します。fetch_add/sub は読み取り-変更-書き込み。compare_exchange は CAS(compare-and-swap)を実装。memory_order_relaxed が最速だが最弱。seq_cst(デフォルト)が最強。シンプルなカウンタとフラグにアトミックを使用します。

cpp
#include <atomic>
std::atomic<int> counter{0};
counter++;  // Atomic increment
counter.fetch_add(1, std::memory_order_relaxed);
bool expected = false;
counter.compare_exchange_strong(expected, true);
// Memory orders: relaxed, acquire, release, seq_cst
26

モダン C++ 機能

構造化束縛

構造化束縛(C++17)はペア、タプル、構造体を分解します。参照には auto&、コピーには auto。map の反復を簡素化します。任意の集約型で動作します。.first/.second よりはるかにクリーンです。

cpp
std::pair p = {1, "hello"};
auto [num, str] = p;  // C++17
std::map<std::string, int> m = {{"a", 1}, {"b", 2}};
for (const auto& [key, value] : m) {
    std::cout << key << ": " << value;
}

std::optional

optional は存在するかもしれないししないかもしれない値を表現します。ポインタやセンチネル値より優れています。has_value() または operator bool でチェック。value() は空の場合スロー、value_or() はデフォルトを提供。結果の生成に失敗する可能性がある関数に使用します。

cpp
#include <optional>
std::optional<int> find(bool found) {
    if (found) return 42;
    return std::nullopt;
}
auto result = find(true);
if (result) std::cout << *result;
// Or: result.value_or(0)

std::variant

variant は型安全なユニオンです。複数の型のうち1つを保持します。visit がビジタ(オーバーロードされたラムダ)を適用。index() が現在の型インデックスを返す。get_if<T> が安全に取得。閉じた型階層のユニオンと継承を置き換えます。

cpp
#include <variant>
std::variant<int, std::string> v;
v = 42;
v = "hello";
std::visit([](auto&& arg) {
    using T = std::decay_t<decltype(arg)>;
    if constexpr (std::is_same_v<T, int>)
        std::cout << "int: " << arg;
    else if constexpr (std::is_same_v<T, std::string>)
        std::cout << "string: " << arg;
}, v);

Ranges(C++20)

Ranges(C++20)は合成可能で遅延なビューを提供します。| 演算子が操作をチェーン。中間コンテナなし。ビューは遅延:計算は反復時に発生。ネストされたアルゴリズム呼び出しよりはるかに読みやすいです。

cpp
#include <ranges>
namespace rv = std::ranges::views;
auto result = std::vector{1, 2, 3, 4, 5}
    | rv::filter([](int x) { return x % 2 == 0; })
    | rv::transform([](int x) { return x * x; });
// Lazy evaluation: no computation until iterated
for (int x : result) std::cout << x;  // 4, 16

コルーチン(C++20)

コルーチン(C++20)は非同期とジェネレータパターンを可能にします。co_yield は一時停止して値を返す。co_await は別のコルーチンを待機。co_return は終了。コンパイラがコルーチンを状態機械に変換。戻り値型(Generator、Task)の実装が必要です。

cpp
#include <coroutine>
Generator<int> counter() {
    for (int i = 0; ; ++i)
        co_yield i;  // Suspend and yield
}
for (int x : counter()) {
    if (x > 5) break;
    std::cout << x;
}
// co_await: wait for async operation
// co_return: finish coroutine
27

よくある落とし穴

ダングリングポインタ

ダングリングポインタは解放されたメモリを指します。アクセスは未定義動作です。手動メモリ管理を回避するためスマートポインタ(unique_ptr、shared_ptr)を使用してください。生ポインタを使用する場合、delete 後に nullptr を設定。AddressSanitizer のようなツールで検出します。

cpp
// BUG: dangling pointer
int* p = new int(42);
delete p;
std::cout << *p;  // Undefined behavior
// FIX: use smart pointers
auto p = std::make_unique<int>(42);
// Or set to nullptr after delete
int* p2 = new int(42);
delete p2; p2 = nullptr;

イテレータ無効化

vector の push_back は再割り当てする可能性があり、すべてのイテレータを無効化。erase はその点以降のイテレータを無効化。list のイテレータは削除された要素以外は安定。各コンテナのイテレータ無効化ルールを確認。迷ったらインデックスを使用。

cpp
std::vector<int> v = {1, 2, 3};
// BUG: iterator invalidated
for (auto it = v.begin(); it != v.end(); ++it) {
    if (*it == 2) v.push_back(4);  // May invalidate it
}
// FIX: use index
for (size_t i = 0; i < v.size(); ++i) {
    if (v[i] == 2) v.push_back(4);
}

未定義動作

未定義動作(UB)はコンパイラに何でもさせることを許可。未初期化変数、範囲外アクセス、null デリファレンス、符号付きオーバーフローはすべて UB。コンパイラは UB がないと仮定して最適化し、驚くべき結果を引き起こします。-fsanitize=undefined で検出。

cpp
int x;  // Uninitialized: UB to read
std::cout << x;  // UB
int arr[5];
arr[5] = 0;  // Out of bounds: UB
int* p = nullptr;
*p;  // Null dereference: UB
// Signed overflow: UB (unsigned wraps)
int big = INT_MAX + 1;  // UB

スライシング

オブジェクトスライシングは派生オブジェクトがベース型にコピーされた時に発生。派生部分が失われます。スライスを避けるため参照またはポインタで渡してください。スライスされたオブジェクトの仮想呼び出しはベースバージョンを呼び出します。ポリモーフィック型は常に参照/ポインタで渡してください。

cpp
class Base { public: virtual ~Base() {} };
class Derived : public Base { int extra; };
void take(Base b) { /* copies Base part only */ }
Derived d;
take(d);  // Sliced: extra lost
// FIX: pass by reference or pointer
void take(const Base& b) { /* full object */ }

静的初期化

翻訳単位間の静的初期化順序は未定義です。あるファイルがまだ初期化されていない別のファイルの static を使用する可能性。関数ローカル static(Meyers Singleton)で修正:初期化は遅延でスレッドセーフ(C++11+)。関数呼び出し経由でアクセス。

cpp
// Static initialization order fiasco
// File1.cpp
int x = computeX();  // May use y
// File2.cpp
int y = computeY();  // May use x
// Order across files is undefined!
// FIX: function-local static
int& getX() {
    static int x = computeX();  // Lazy, thread-safe
    return x;
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.