C++ 言語仕様ガイド

他言語の経験者が 2〜3 時間でざっと読み通せるリファレンスです。C++ 固有の落とし穴(未定義動作・メモリ管理・オブジェクトライフタイム)を重点的に解説します。

C++17/C++20経験者向けコード例付き
01

変数・型・基本演算

基礎
基本型基礎

C++ の整数型はサイズが処理系依存。確定サイズが必要なら int32_tuint64_t 等を使う。booltrue/falsechar は符号が処理系依存なので文字は unsigned charstd::string を使う。

C++
#include <cstdint>
#include <iostream>

int main() {
    int           a = 42;
    long long     b = 9'223'372'036'854'775'807LL; // C++14: 数値区切り
    double        c = 3.14;
    bool          d = true;
    std::size_t   e = sizeof(int); // インデックス・サイズは size_t
    int32_t  x = -100;   // 確定 32bit
    uint64_t y = 100ULL; // 確定 64bit 符号なし
    std::cout << a << " " << b << " " << c << "
";
    std::cout << std::boolalpha << d << "
"; // "true"
}
int は最低 16bit の保証しかない(多くの環境では 32bit)。sizeof(int) == 4 を前提にすると移植性が壊れる。クロスプラットフォームコードでは の固定幅型を使うこと。
auto による型推論基礎

C++11 以降、auto で型推論が可能。ただし参照や const は推論されないため auto&const auto& と明示が必要。decltype は式の型を取り出す。

C++
auto x = 42;       // int
auto y = 3.14;     // double
auto s = "hello";  // const char* (std::string ではない!)

std::vector<int> v = {1, 2, 3};
auto it = v.begin(); // std::vector<int>::iterator

// 参照で受け取る場合は明示
auto& ref   = v[0];       // int&(コピーなし)
const auto& cref = v[0];  // const int&(読み取り専用)

// decltype
int a = 1;
decltype(a) b = 2; // int
auto s = "hello";std::string ではなく const char* になる。文字列として扱うなら auto s = std::string{"hello"}; と書くこと。
const と constexpr基礎

const は実行時定数(値が変わらないことを保証)、constexpr はコンパイル時定数(コンパイル時に値が確定)。constexpr 変数はテンプレート引数や配列サイズに使える。C++11 以降 constexpr 関数も書ける。

C++
const int MAX = 100;          // 実行時定数
constexpr int BUF = 256;      // コンパイル時定数

constexpr int square(int n) { return n * n; }

int arr[BUF];                 // OK: constexpr なので配列サイズに使える
template<int N> struct Foo {};
Foo<square(4)> f;             // OK: コンパイル時に 16 が確定

const int rt = someFunc();    // OK: 実行時に決まる
// constexpr int err = someFunc(); // NG: constexpr 関数でなければ不可
型変換(named cast)基礎

C++ には 4 種の named cast がある。C スタイルキャスト (Type)x は危険なので使ってはいけない。static_cast が最も一般的。

C++
double d = 3.7;
int i = static_cast<int>(d);       // 3(切り捨て)

// reinterpret_cast: ビットレベルの再解釈(危険)
uintptr_t addr = reinterpret_cast<uintptr_t>(&d);

// const_cast: const を外す(元が const なら UB)
const int ci = 42;
// int& ri = const_cast<int&>(ci); // 元が const なので UB!

// dynamic_cast: 実行時の安全なダウンキャスト(仮想関数を持つ型のみ)
Base* b = new Derived();
Derived* dp = dynamic_cast<Derived*>(b); // 成功: 非 null
if (!dp) { /* 変換失敗 */ }
C スタイルキャスト (int)xconst も外せる万能で危険なキャスト。誤った変換をコンパイラが検出できないため、必ず named cast を使うこと。
02

制御フロー

基礎
if / switch基礎

C++17 で if の初期化文が追加された。switchcase の fall-through がデフォルト(break を忘れやすい)。意図的な fall-through は [[fallthrough]] 属性で明示。

C++
// C++17: if 初期化文(スコープを限定できる)
if (auto it = map.find(key); it != map.end()) {
    use(it->second); // it はこの if ブロック内にスコープが限定
}

// switch の fall-through
switch (n) {
    case 1:
        doA();
        [[fallthrough]]; // 意図的な fall-through を明示
    case 2:
        doB();
        break;
    default:
        doDefault();
}
switchbreak を忘れると次の case に処理が流れる(fall-through)。意図しない fall-through はバグの温床。コンパイラの -Wimplicit-fallthrough 警告を有効にすること。
ループ(for / while / 範囲 for)基礎

C++11 の範囲 for 文(range-based for)でコンテナを簡潔に走査できる。コピーを避けるため要素は const auto& で受け取るのが基本。変更する場合は auto&

C++
std::vector<std::string> names = {"Alice", "Bob", "Carol"};

// コピーなし読み取り
for (const auto& name : names) {
    std::cout << name << "
";
}

// 変更する場合
for (auto& name : names) {
    name += "!";
}

// インデックス付き: 従来の for
for (std::size_t i = 0; i < names.size(); ++i) {
    std::cout << i << ": " << names[i] << "
";
}
範囲 for で auto name と書くとコピーが発生する。文字列やコンテナの要素は const auto& を習慣にすること。プリミティブ型(int 等)はコピーでよい。
多重ループの脱出(goto の正当なユースケース)基礎

C++ で goto が有用なのは多重ループを一気に脱出する場面。ただし RAII やラムダを使った代替手段の方が可読性が高い。

C++
// goto による多重ループ脱出(C++ では稀)
for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
        if (found(i, j)) goto done;
    }
}
done:;

// 推奨: ラムダで包んで return
auto search = [&]() -> std::pair<int,int> {
    for (int i = 0; i < N; ++i)
        for (int j = 0; j < M; ++j)
            if (found(i, j)) return {i, j};
    return {-1, -1};
};
auto [r, c] = search();
03

関数

基礎
関数オーバーロードとデフォルト引数基礎

C++ では同名で引数の型・数が異なる関数を複数定義できる(オーバーロード)。デフォルト引数は宣言側に書き、末尾の引数から順に省略可能。

C++
void print(int n)              { std::cout << "int: " << n; }
void print(double d)           { std::cout << "double: " << d; }
void print(const std::string& s){ std::cout << "str: " << s; }

print(42);      // int: 42
print(3.14);    // double: 3.14
print("hi");    // str: hi

// デフォルト引数(末尾から省略可能)
void connect(const std::string& host, int port = 80, bool tls = false);

connect("example.com");            // port=80, tls=false
connect("example.com", 443, true); // 全部指定
デフォルト引数は宣言(ヘッダ)側にのみ書く。実装側にも書くと再定義エラー。オーバーロードとデフォルト引数が混在すると呼び出しが曖昧になる場合がある。
ラムダ式基礎

C++11 のラムダは [キャプチャ](引数) -> 戻り値型 { 本体 } の形。キャプチャは [=](コピー)・[&](参照)・個別指定が可能。戻り値型は多くの場合推論できるので省略可能。

C++
int offset = 10;

// キャプチャ(コピー)
auto addOffset = [offset](int x) { return x + offset; };

// キャプチャ(参照)
auto increment = [&offset]() { ++offset; };

// STL アルゴリズムへの渡し方
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; }); // 降順

// std::function で型消去(オーバーヘッドあり)
std::function<int(int)> fn = addOffset;
std::cout << fn(5) << "
"; // 15
[&] でキャプチャしたラムダを関数の戻り値として返すと、ローカル変数への参照がダングリングになる。非同期コールバックでも同様の危険がある。
inline 関数と constexpr 関数基礎

inline はヘッダに関数定義を書いても ODR 違反にならないためのキーワード(インライン展開の指示ではない)。constexpr 関数はコンパイル時にも実行時にも呼べる。

C++
// ヘッダに書ける(ODR 違反にならない)
inline int clamp(int v, int lo, int hi) {
    return v < lo ? lo : v > hi ? hi : v;
}

// constexpr 関数: コンパイル時に評価可能
constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr int f5 = factorial(5); // コンパイル時に 120
int arr[factorial(4)];           // 配列サイズにも使える

// 実行時にも呼べる
int n = 6;
int fn = factorial(n); // 実行時に計算
04

ポインタと参照

基礎
ポインタの基礎基礎

ポインタはメモリアドレスを格納する変数。* でデリファレンス(値アクセス)、& でアドレス取得。nullptr(C++11)は NULL の代替で型安全。

C++
int x = 42;
int* p = &x;         // p は x のアドレスを保持
std::cout << *p;     // 42(デリファレンス)
*p = 100;            // x も 100 になる

// nullptr チェック
int* q = nullptr;
if (q) { /* null でなければ処理 */ }

// 構造体メンバへのアクセス
struct Point { int x, y; };
Point pt{1, 2};
Point* pp = &pt;
std::cout << pp->x;  // (*pp).x と同じ
未初期化のポインタを使うと不定値を参照(未定義動作)。必ず nullptr か有効なアドレスで初期化すること。NULL は C の慣習なので C++ では nullptr を使う。
参照(&)基礎

参照はポインタの安全版。宣言時に必ず初期化し、後から別の変数を指すことができない。const T& で大きなオブジェクトをコピーなしで読み取れる。

C++
int a = 1;
int& ref = a;  // ref は a の別名
ref = 99;      // a == 99

// const 参照: コピーなし読み取り
void printStr(const std::string& s) {
    std::cout << s; // コピーなし
}

// 参照を返す関数(変更可能)
int& getFirst(std::vector<int>& v) { return v[0]; }
getFirst(v) = 999;  // v[0] を直接変更
関数からローカル変数の参照を返すと、スコープを抜けた後に参照がダングリングになり未定義動作。コンパイラが警告を出すが見逃しやすい。
const とポインタ基礎

const の位置で意味が変わる。右から左に読むと理解しやすい。const T* は「指す先が const」、T* const は「ポインタ自体が const」。

C++
int x = 1, y = 2;

const int* p1 = &x;  // "const な int へのポインタ"
// *p1 = 9;           // NG: 指す先を変更不可
p1 = &y;             // OK: p1 自体は変えられる

int* const p2 = &x;  // "int へのconstポインタ"
*p2 = 9;             // OK: 指す先は変えられる
// p2 = &y;           // NG: p2 自体は変えられない

const int* const p3 = &x; // 両方 const
関数ポインタと std::function基礎

関数ポインタは型が複雑なので autousing で簡略化。std::function はあらゆる callable(ラムダ・関数ポインタ・ファンクタ)を格納できる型消去ラッパー。

C++
int add(int a, int b) { return a + b; }

// 関数ポインタ
int (*fp)(int, int) = add;
fp(1, 2); // 3

// auto で簡略化
auto fp2 = add; // int(*)(int,int)

// using でエイリアス
using BinOp = int(*)(int, int);
BinOp op = add;

// std::function: ラムダも格納可
std::function<int(int,int)> fn = [](int a, int b){ return a + b; };
fn(3, 4); // 7
std::function は型消去のため仮想関数と同程度のオーバーヘッドがある。ホットパスでは auto でラムダを直接保持するか、テンプレートを使うこと。
05

配列と文字列

基礎
C 配列と std::array基礎

C 配列は関数に渡すとポインタに decay してサイズ情報が失われる。std::array は固定長で decay しないため安全で STL アルゴリズムとも親和性が高い。

C++
// C 配列(非推奨: 関数に渡すと decay する)
int ca[5] = {1, 2, 3, 4, 5};
// sizeof(ca)/sizeof(ca[0]) で要素数取得(危険)

// std::array(推奨: 固定長)
#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
arr.size();    // 5(メンバ関数でサイズ取得)
arr.at(10);    // 範囲チェック付き(越えると std::out_of_range)

// std::vector(動的サイズ)
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.size(); // 4

// C++20: std::span(サイズ付き非所有ビュー)
#include <span>
void process(std::span<int> data) { /* 配列もvectorも受け取れる */ }
C 配列を void f(int arr[]) で受け取ると void f(int* arr) と同じになりサイズ情報が消える。C++20 の std::spanstd::vector/std::array を渡すこと。
std::string と std::string_view基礎

std::string はヒープ確保の動的文字列。std::string_view(C++17)はコピーなしで文字列を参照する軽量型で、読み取り専用の引数に最適。

C++
#include <string>
#include <string_view>

std::string s = "Hello, World!";
s += " Bye";               // 連結
s.size();                  // 文字数
s.substr(0, 5);            // "Hello"
s.find("World");           // 7(見つからなければ std::string::npos)
s.empty();                 // false

// string_view: コピーなし参照
std::string_view sv = s;
void readOnly(std::string_view v); // const std::string& より軽量

// raw string リテラル(バックスラッシュ・改行そのまま)
std::string path  = R"(C:UsersaliceDocuments)";
std::string regex = R"(d+.d+)";
std::string_view は元の文字列が存在する間のみ有効。std::string の一時オブジェクトに string_view を作り、その一時オブジェクトが破棄されるとダングリング。
06

クラスとオブジェクト

基礎
クラス定義とアクセス指定子基礎

class はデフォルトが privatestruct はデフォルトが public。それ以外は同じ。データメンバを private にし、メンバ関数でアクセスを制御するのが基本。

C++
class Person {
public:
    Person(std::string name, int age) : name_(name), age_(age) {}

    const std::string& name() const { return name_; }
    int age() const { return age_; }
    void birthday() { ++age_; }

private:
    std::string name_;
    int age_;
};

Person p("Alice", 30);
p.birthday();
std::cout << p.name() << " " << p.age() << "
"; // Alice 31
コンストラクタと初期化リスト基礎

初期化リスト(: メンバ(値))はコンストラクタ本体での代入より効率的。const メンバや参照メンバは初期化リストでしか初期化できない。C++11 以降、委譲コンストラクタで重複を減らせる。

C++
class Buffer {
public:
    // 初期化リスト: コンストラクタ本体前に初期化
    Buffer(std::size_t size, char fill = 0)
        : size_(size), data_(new char[size]) {
        std::fill(data_, data_ + size_, fill);
    }

    // 委譲コンストラクタ (C++11)
    Buffer() : Buffer(256) {}

    ~Buffer() { delete[] data_; } // デストラクタで解放

private:
    std::size_t size_;
    char* data_;
};
const メンバと参照メンバは初期化リストでのみ初期化できる。初期化リストの順序はメンバ宣言順と一致させること(不一致はコンパイラ警告。順序が違うと初期化順が意図と異なる)。
const メンバ関数と static・mutable基礎

const メンバ関数はオブジェクトの状態を変更しないことを保証。static メンバはオブジェクトではなくクラスに属する。mutable はキャッシュなど論理的に const でも変更したいケースに使う。

C++
class Counter {
public:
    void increment() { ++count_; }         // 非 const: 状態変更
    int value() const { return count_; }   // const: 変更不可

    // mutable: const 関数内でも変更可
    int cached() const {
        if (!cached_) { cache_ = expensive(); cached_ = true; }
        return cache_;
    }

    static int instances() { return s_count_; } // クラスメソッド

private:
    int count_ = 0;
    static int s_count_;       // .cpp で定義が必要
    mutable bool cached_ = false;
    mutable int cache_ = 0;
};
= default / = delete基礎

= default でコンパイラ生成の特殊メンバ関数を明示的に要求、= delete で禁止できる。コピー禁止クラス(ファイルハンドル・mutex 等)を作る際に使う。

C++
class NonCopyable {
public:
    NonCopyable() = default;
    ~NonCopyable() = default;

    // コピー禁止
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;

    // ムーブは許可
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

NonCopyable a;
// NonCopyable b = a; // コンパイルエラー
NonCopyable c = std::move(a); // OK
07

継承と多態性

基礎
継承の基本基礎

class Derived : public Base で公開継承(is-a 関係)。基底クラスのコンストラクタは初期化リストで明示的に呼ぶ。デストラクタは必ず virtual にすること。

C++
class Animal {
public:
    explicit Animal(std::string name) : name_(std::move(name)) {}
    const std::string& name() const { return name_; }
    virtual std::string sound() const { return "..."; }
    virtual ~Animal() = default; // 仮想デストラクタ必須

private:
    std::string name_;
};

class Dog : public Animal {
public:
    explicit Dog(std::string name) : Animal(std::move(name)) {}
    std::string sound() const override { return "Woof"; }
};

std::unique_ptr<Animal> a = std::make_unique<Dog>("Rex");
std::cout << a->sound(); // "Woof"(多態性)
基底クラスのデストラクタが virtual でない場合、Base* から delete すると派生クラスのデストラクタが呼ばれない(リソースリーク・未定義動作)。必ず virtual ~Base() = default; を書くこと。
仮想関数・override・final基礎

virtual で宣言した関数は実行時に動的ディスパッチされる。override で誤ったシグネチャをコンパイル時に検出できる。= 0 で純粋仮想関数(そのクラスはインスタンス化不可の抽象クラス)。

C++
class Shape {
public:
    virtual double area() const = 0;  // 純粋仮想: インスタンス化不可
    virtual void draw() const {}      // デフォルト実装あり
    virtual ~Shape() = default;
};

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

private:
    double r_;
};

// final: これ以上継承・オーバーライドを禁止
class SpecialCircle final : public Circle {
public:
    using Circle::Circle;
};
override なしで仮想関数と同名でも引数型が違う関数を書くと、オーバーライドではなく全く別の関数になる(シャドーイング)。必ず override を付けること。
dynamic_cast と RTTI基礎

dynamic_cast は実行時に安全なダウンキャスト(基底 → 派生)。ポインタで失敗すると nullptr、参照で失敗すると std::bad_cast をスロー。仮想関数を持つ型にのみ使える。

C++
Shape* s = new Circle(5.0);

// ポインタ: 失敗時は nullptr
if (Circle* c = dynamic_cast<Circle*>(s)) {
    std::cout << "Circle, area=" << c->area();
}

// 参照: 失敗時は std::bad_cast
try {
    Circle& c = dynamic_cast<Circle&>(*s);
} catch (const std::bad_cast& e) {
    std::cerr << "Bad cast
";
}

delete s;
dynamic_cast の多用は設計上の問題を示すことが多い。仮想関数の追加やビジターパターンの導入を先に検討すること。
08

演算子オーバーロード

応用
演算子オーバーロードの基本応用

多くの演算子を独自型に対してオーバーロードできる。メンバ関数として定義するか、非メンバ関数(friend)として定義する。<<std::ostream& を返し連鎖呼び出しを可能にする。

C++
class Vec2 {
public:
    double x, y;
    Vec2(double x, double y) : x(x), y(y) {}

    Vec2 operator+(const Vec2& o) const { return {x+o.x, y+o.y}; }
    Vec2& operator+=(const Vec2& o) { x+=o.x; y+=o.y; return *this; }
    bool operator==(const Vec2& o) const { return x==o.x && y==o.y; }

    // 出力演算子: friend 非メンバ関数
    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)
Rule of Three / Five / Zero応用

デストラクタ・コピーコンストラクタ・コピー代入のいずれかを定義したら 3 つとも定義するのが Rule of Three。C++11 以降はムーブ操作 2 つも加えて Rule of Five。スマートポインタ等を使えば何も定義不要(Rule of Zero)が理想。

C++
class MyBuf {
public:
    explicit MyBuf(std::size_t n) : data_(new int[n]), size_(n) {}

    // コピーコンストラクタ(ディープコピー)
    MyBuf(const MyBuf& o) : data_(new int[o.size_]), size_(o.size_) {
        std::copy(o.data_, o.data_ + size_, data_);
    }
    // コピー代入(コピー&スワップイディオム)
    MyBuf& operator=(MyBuf o) { swap(o); return *this; }

    // ムーブコンストラクタ
    MyBuf(MyBuf&& o) noexcept : data_(o.data_), size_(o.size_) {
        o.data_ = nullptr; o.size_ = 0;
    }
    // ムーブ代入
    MyBuf& operator=(MyBuf&& o) noexcept { swap(o); return *this; }

    ~MyBuf() { delete[] data_; }

private:
    void swap(MyBuf& o) noexcept {
        std::swap(data_, o.data_); std::swap(size_, o.size_);
    }
    int* data_;
    std::size_t size_;
};
コンパイラが生成するデフォルトのコピーはシャローコピー(ポインタをそのまま複製)。動的メモリを持つクラスでディープコピーを書かないと二重解放(未定義動作)になる。
三方比較演算子 <=> (C++20)応用

C++20 の <=> を定義するだけで <><=>= が自動生成される。= default で全メンバの辞書順比較も自動化できる。

C++
#include <compare>

class Point {
public:
    int x, y;
    // <=> を定義すると <, >, <=, >= が自動生成
    auto operator<=>(const Point&) const = default;
    bool operator==(const Point&) const = default;
};

Point a{1, 2}, b{1, 3};
std::cout << (a < b);  // true(x が等しければ y で比較)
std::cout << (a == b); // false
09

メモリ管理と RAII

応用
new / delete応用

new はヒープからメモリを確保しコンストラクタを呼ぶ。delete はデストラクタを呼び解放。配列は new[]/delete[] のペアで。直接使うのは避けスマートポインタを使うこと。

C++
// 単一オブジェクト
int* p = new int(42);
delete p;
p = nullptr; // dangling pointer 防止

// 配列
int* arr = new int[10]{};
delete[] arr; // delete[] を使う(delete だと UB)

// 確保失敗: std::bad_alloc がスロー
try {
    int* big = new int[1'000'000'000'000LL];
} catch (const std::bad_alloc& e) {
    std::cerr << "Out of memory
";
}

// nothrow バージョン(失敗時 nullptr)
int* q = new (std::nothrow) int;
if (!q) { /* メモリ確保失敗 */ }
new[] で確保したメモリを delete[] なし)で解放すると未定義動作。newdeletenew[]delete[] と必ず対応させること。現代 C++ ではスマートポインタを使えば不要。
RAII 原則応用

Resource Acquisition Is Initialization。リソース(メモリ・ファイル・ロック等)をオブジェクトのライフタイムに結び付ける。コンストラクタで取得、デストラクタで解放。例外が来てもデストラクタは保証される。

C++
// RAII の例: ファイルハンドラ
class FileHandle {
public:
    explicit FileHandle(const char* path)
        : fp_(std::fopen(path, "r")) {
        if (!fp_) throw std::runtime_error("cannot open");
    }
    ~FileHandle() { if (fp_) std::fclose(fp_); } // 例外でも必ず閉じる

    FILE* get() { return fp_; }

    // コピー禁止(ハンドルの二重解放を防ぐ)
    FileHandle(const FileHandle&) = delete;
    FileHandle& operator=(const FileHandle&) = delete;

private:
    FILE* fp_;
};

{
    FileHandle f("data.txt");
    // 例外が来ても ~FileHandle() が呼ばれてクローズされる
} // スコープを抜けると自動クローズ
未定義動作(UB)応用

C++ の未定義動作(UB)はコンパイラが何をしてもよい状態。クラッシュ・誤った結果・セキュリティホールにつながる。よくある UB を把握して避けること。

C++
// よくある未定義動作の例(これらを書いてはいけない)

// 1. 符号付き整数オーバーフロー(unsigned は wrap-around で定義済み)
int x = INT_MAX;
// int y = x + 1; // UB!

// 2. 境界外アクセス
int arr[5];
// arr[5] = 0; // UB!

// 3. ヌルポインタデリファレンス
int* p = nullptr;
// *p = 42; // UB!

// 4. ダングリング参照
int& dangle() {
    int local = 1;
    return local; // UB: ローカル変数の参照を返す
}

// 5. 初期化前の値を読む
int uninit;
// std::cout << uninit; // UB!
UB はコンパイラ最適化により「消える」ことがある。デバッグビルドでは動いて、リリースビルドで壊れるケースがある。AddressSanitizer・UBSanitizer を使って早期発見すること。
10

スマートポインタ

応用
unique_ptr応用

単独所有権を持つスマートポインタ。コピー不可・ムーブのみ可。make_unique(args) で生成(例外安全)。生ポインタと同等のパフォーマンスでオーバーヘッドはほぼゼロ。

C++
#include <memory>

auto p = std::make_unique<int>(42);
std::cout << *p;  // 42(スコープ終了時に自動解放)

// 所有権の移転
auto q = std::move(p); // p は nullptr になる
if (!p) std::cout << "p is null
";

// カスタムデリータ
auto fp = std::unique_ptr<FILE, decltype(&fclose)>(
    fopen("f.txt", "r"), &fclose);

// ファクトリ関数パターン
std::unique_ptr<Shape> makeShape(ShapeType t) {
    switch (t) {
        case CIRCLE: return std::make_unique<Circle>(1.0);
        default:     return std::make_unique<Square>(1.0);
    }
}
shared_ptr と weak_ptr応用

shared_ptr は参照カウント方式で複数の所有者を持てる。カウントが 0 で解放。weak_ptr は所有権を持たない弱参照で循環参照を防ぐ。make_shared は参照カウントとオブジェクトを一度に確保して効率的。

C++
auto sp1 = std::make_shared<std::string>("hello");
auto sp2 = sp1;              // 参照カウント: 2
std::cout << sp1.use_count(); // 2

// weak_ptr: 循環参照防止
struct Node {
    std::shared_ptr<Node> next;
    std::weak_ptr<Node>   prev; // weak でないと循環参照でリーク
};

// weak_ptr の使い方
std::weak_ptr<std::string> wp = sp1;
if (auto locked = wp.lock()) { // expired してなければ shared_ptr
    std::cout << *locked;
}
shared_ptr の循環参照(A が B を所有、B が A を所有)は参照カウントが 0 にならずメモリリーク。片方を weak_ptr にすること。make_sharednew + shared_ptr より効率的。
11

例外処理

基礎
try / catch / throw基礎

throw で任意の型をスローできるが std::exception 派生クラスを使うのが慣習。catch(...) で全例外を捕捉。捕捉されない例外は std::terminate が呼ばれる。

C++
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0.0) throw std::invalid_argument("division by zero");
    return a / b;
}

try {
    double r = divide(10.0, 0.0);
} catch (const std::invalid_argument& e) {
    std::cerr << "Invalid: " << e.what() << "
";
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << "
";
} catch (...) {
    std::cerr << "Unknown exception
";
}

// 独自例外クラス
class AppError : public std::runtime_error {
public:
    AppError(int code, const std::string& msg)
        : std::runtime_error(msg), code_(code) {}
    int code() const { return code_; }
private:
    int code_;
};
noexcept と例外安全性基礎

noexcept は例外をスローしないことを宣言。ムーブコンストラクタに付けると vector リサイズ時にムーブが使われパフォーマンスが向上する。noexcept な関数が例外をスローすると std::terminate

C++
void cleanup() noexcept { /* 例外を投げない */ }

// ムーブには noexcept を付けるべき(vector の最適化に影響)
class MyClass {
public:
    MyClass(MyClass&& o) noexcept : data_(o.data_) {
        o.data_ = nullptr;
    }
};

// 例外安全性の 3 レベル:
// 1. Basic guarantee: 例外後もリソースリークなし(最低保証)
// 2. Strong guarantee: 例外後は操作前の状態(トランザクション的)
// 3. No-throw guarantee: 例外が発生しない(noexcept)
vector のリサイズ時、ムーブコンストラクタが noexcept でないと安全のためコピーが使われる(パフォーマンス低下)。ムーブ操作には必ず noexcept を付けること。
12

STL コンテナ

基礎
std::vector基礎

最も使うコンテナ。動的配列でメモリは連続。末尾への追加は平均 O(1)。reserve で事前にメモリ確保するとリサイズを防げる。emplace_back はコピーなしでその場に構築。

C++
std::vector<int> v;
v.reserve(100);           // メモリ事前確保(サイズは変わらない)
v.push_back(1);
v.emplace_back(2);        // その場で構築(std::string 等でコピー不要)
v.pop_back();
v.insert(v.begin(), 0);  // 先頭挿入(O(n))
v.erase(v.begin());      // 先頭削除(O(n))

v[0];        // 範囲チェックなし(高速)
v.at(0);     // 範囲チェックあり(越えると std::out_of_range)
v.front();   // 先頭要素
v.back();    // 末尾要素
v.data();    // 生ポインタ(C API に渡す場合)
push_backinsert でリサイズが発生すると、既存のイテレータ・ポインタ・参照がすべて無効になる(ダングリング)。イテレータを保持したまま push_back しないこと。
std::map と std::unordered_map基礎

std::map は赤黒木でキー順に保持(O(log n))。std::unordered_map はハッシュテーブルで平均 O(1)。順序不要なら unordered_map の方が速い。

C++
std::unordered_map<std::string, int> scores;
scores["Alice"] = 90;

// 安全なアクセス: [] はキーがなければデフォルト値で挿入してしまう
if (auto it = scores.find("Carol"); it != scores.end()) {
    std::cout << it->second;
} else {
    std::cout << "not found";
}

// 挿入(既存キーは上書きしない)
auto [it, ok] = scores.try_emplace("Eve", 95); // C++17

// C++17 構造化束縛で全要素走査
for (const auto& [key, val] : scores) {
    std::cout << key << ": " << val << "
";
}
map[key] はキーが存在しない場合にデフォルトコンストラクタで要素を生成・挿入する。const map[] は使えない。存在確認は findcontains(C++20)を使うこと。
set / unordered_set / queue / stack基礎

std::set は重複なしの順序付きコレクション。std::queue は FIFO、std::stack は LIFO。std::priority_queue で優先キュー。いずれも内部実装は別のコンテナのアダプタ。

C++
// set: 重複なし・ソート済み
std::set<int> s = {3, 1, 4, 1, 5}; // {1, 3, 4, 5}
s.insert(9);
s.count(3);   // 0 か 1
s.contains(3); // C++20: bool

// queue: FIFO
std::queue<std::string> q;
q.push("first"); q.push("second");
q.front(); // "first"
q.pop();   // front を削除

// stack: LIFO
std::stack<int> st;
st.push(1); st.push(2);
st.top(); // 2
st.pop(); // 2 を削除

// priority_queue: 最大値優先
std::priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
pq.top(); // 4
13

STL アルゴリズムとイテレータ

応用
イテレータ応用

STL アルゴリズムはイテレータを通じてコンテナ非依存に動作する。カテゴリ(入力・出力・前向き・双方向・ランダムアクセス)によって使えるアルゴリズムが異なる。

C++
std::vector<int> v = {1, 2, 3, 4, 5};

// begin/end: 前から走査
for (auto it = v.begin(); it != v.end(); ++it) { ... }

// rbegin/rend: 後ろから走査
for (auto it = v.rbegin(); it != v.rend(); ++it) { ... }

// イテレータアダプタ
#include <iterator>
// ostream_iterator で出力
std::copy(v.begin(), v.end(),
    std::ostream_iterator<int>(std::cout, " ")); // 1 2 3 4 5

// back_inserter でコンテナ末尾に追加
std::vector<int> out;
std::copy(v.begin(), v.end(), std::back_inserter(out));
std::sort / find / transform / accumulate応用

ヘッダに豊富なアルゴリズム。std::sort はイントロソート(O(n log n))。remove_if + erase のイディオムでフィルタリング。

C++
#include <algorithm>
#include <numeric>

std::vector<int> v = {5, 3, 1, 4, 2};
std::sort(v.begin(), v.end());                            // 昇順
std::sort(v.begin(), v.end(), std::greater<int>());       // 降順

auto it = std::find(v.begin(), v.end(), 3);
if (it != v.end()) { /* 見つかった */ }

// Erase-Remove イディオム: 条件に合う要素を削除
v.erase(std::remove_if(v.begin(), v.end(),
    [](int x){ return x % 2 == 0; }), v.end());

// transform: 変換
std::vector<int> doubled;
std::transform(v.begin(), v.end(), std::back_inserter(doubled),
    [](int x){ return x * 2; });

// accumulate: 集約(初期値が必要)
int sum = std::accumulate(v.begin(), v.end(), 0);
std::remove / std::remove_if は要素を削除せず、削除対象を末尾に移動して新しい末尾イテレータを返すだけ。vector::erase と組み合わせて実際に削除すること(Erase-Remove イディオム)。
std::ranges (C++20)応用

C++20 の はアルゴリズムをパイプライン形式で記述できる。views は遅延評価でコピーが不要。コンテナ全体を引数に取るオーバーロードも追加された。

C++
#include <ranges>

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

// パイプライン(遅延評価: 走査時まで計算しない)
auto result = v
    | std::views::filter([](int x){ return x % 2 == 0; }) // 偶数
    | std::views::transform([](int x){ return x * x; });  // 二乗

for (int x : result) {
    std::cout << x << " "; // 4 16 36
}

// ranges::sort: begin()/end() 省略可
std::ranges::sort(v);
auto it = std::ranges::find(v, 3);
bool found = std::ranges::contains(v, 3); // C++23
14

テンプレート

応用
関数テンプレート応用

template で型パラメータを取る関数を定義。呼び出し時に型が推論されるため明示指定は少ない。インスタンス化はコンパイル時に行われる。

C++
template<typename T>
T maximum(T a, T b) { return a > b ? a : b; }

maximum(3, 5);         // T = int(推論)
maximum(3.0, 5.0);     // T = double
maximum<int>(3, 5);    // T を明示

// 複数の型パラメータ
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { return a + b; }

// 非型パラメータ(値をテンプレート引数に)
template<std::size_t N>
void printArray(const int (&arr)[N]) {
    for (std::size_t i = 0; i < N; ++i) std::cout << arr[i] << " ";
}
int a[] = {1, 2, 3};
printArray(a); // N = 3 が推論
テンプレートはヘッダファイルに実装を書く必要がある(.cpp に分けると未定義シンボルエラー)。宣言と定義を同じヘッダに置くこと。
クラステンプレートと特殊化応用

クラスにも型パラメータを持たせられる。std::vector のように使う。特殊化で特定の型に対して別実装を提供できる。

C++
template<typename T>
class Stack {
public:
    void push(T val) { data_.push_back(std::move(val)); }
    T pop() { T v = std::move(data_.back()); data_.pop_back(); return v; }
    bool empty() const { return data_.empty(); }
    std::size_t size() const { return data_.size(); }
private:
    std::vector<T> data_;
};

Stack<int> si;
si.push(1); si.push(2);

// 完全特殊化: bool に対して別実装
template<>
class Stack<bool> {
    // ビット単位で保存する効率的な実装...
};
concept (C++20)応用

C++20 の concept でテンプレート引数に制約を付けられ、エラーメッセージが大幅に改善される。requires 式で型が特定の操作をサポートするか検査できる。

C++
#include <concepts>

// concept の定義
template<typename T>
concept Addable = requires(T a, T b) { a + b; };

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

// concept で制約されたテンプレート関数
template<Addable T>
T sum(T a, T b) { return a + b; }

// requires 節でも書ける
template<typename T>
    requires Numeric<T>
T square(T x) { return x * x; }

sum(1, 2);        // OK: int は Addable
// sum(std::vector<int>{}, {}); // エラー: Addable を満たさない(明快なエラー)
可変引数テンプレート応用

template で任意個の型パラメータを受け取れる。C++17 のフォールド式でパックに対する演算を簡潔に書ける。

C++
// 可変引数テンプレート
template<typename... Args>
void print(Args&&... args) {
    (std::cout << ... << args) << "
"; // C++17 フォールド式
}
print(1, " ", 2.0, " ", "hello"); // 1 2 hello

// 型の数を取得
template<typename... Ts>
constexpr std::size_t count() { return sizeof...(Ts); }
count<int, double, std::string>(); // 3

// 完全転送と組み合わせ(make_unique の実装例)
template<typename T, typename... Args>
std::unique_ptr<T> myMakeUnique(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
15

ムーブセマンティクス

応用
右辺値参照と std::move応用

C++11 の右辺値参照(T&&)で、もうすぐ破棄されるオブジェクトのリソースを「盗む」ムーブが可能。std::move は左辺値を右辺値参照にキャストするだけで、実際の移動はムーブコンストラクタが行う。

C++
std::string a = "hello";
std::string b = std::move(a); // a のバッファを b に移す。a は空文字列に

// 関数の戻り値は自動でムーブ(RVO/NRVO でさらに最適化)
std::vector<int> makeVec() {
    std::vector<int> v = {1, 2, 3};
    return v; // コンパイラが最適化(コピーなし)
}

// 関数引数でのムーブ(所有権の移転)
class Repo {
    std::vector<int> data_;
public:
    void store(std::vector<int> v) { data_ = std::move(v); }
};
Repo r;
std::vector<int> big(1000);
r.store(std::move(big)); // big の中身を移転
std::move 後のオブジェクトは「有効だが未規定の状態」(通常は空)。移動元の変数を再利用する場合は再代入してから使うこと。
完全転送(std::forward)応用

テンプレートパラメータの T&& は「転送参照(forwarding reference)」になる。std::forward で左辺値なら左辺値として、右辺値なら右辺値として次の関数に転送できる。

C++
// 完全転送: 引数の値カテゴリを保持して渡す
template<typename T, typename... Args>
std::unique_ptr<T> myMakeUnique(Args&&... args) {
    return std::unique_ptr<T>(
        new T(std::forward<Args>(args)...)
    );
}

struct Widget {
    Widget(int x, std::string s) {}
};

auto w = myMakeUnique<Widget>(42, std::string("hello"));
// 42 は int&&(右辺値)として転送
// std::string("hello") は string&&(右辺値)として転送
std::forwardstd::move は別物。std::forward は値カテゴリを保持して転送するためのもの。転送参照でない場所で std::forward を使うと意図しないムーブが発生する。
RVO / NRVO とコピー省略応用

C++17 でコピー省略(copy elision)が一部必須化。return に右辺値(一時オブジェクト)を返す場合はコピーもムーブも不要。名前付き戻り値最適化(NRVO)は任意だがほとんどのコンパイラが実装。

C++
std::string make() {
    std::string s = "hello";
    return s; // NRVO: s のコンストラクタは 1 回だけ(コピー・ムーブなし)
}

// C++17 必須のコピー省略
std::string s = make(); // コピーもムーブも発生しない

// 注意: std::move して返すと NRVO を妨害する場合がある
std::string makeBad() {
    std::string s = "hello";
    return std::move(s); // NRVO が無効になる可能性(パフォーマンス低下)
}
return std::move(local); はローカル変数に対して NRVO を妨害する可能性がある。ローカル変数をそのまま return すれば良い。std::move が必要なのはメンバ変数や引数を返す場合。
16

モダン C++(C++17/20)

応用
構造化束縛と if constexpr応用

C++17 の構造化束縛 auto [a, b] = pair; で複数の戻り値を分解できる。if constexpr はテンプレートの分岐をコンパイル時に解決し、コンパイルされない方の型チェックをスキップする。

C++
// 構造化束縛
auto [x, y] = std::make_pair(1, 2.0);
auto [it, ok] = myMap.insert({"key", 42}); // C++17

std::tuple<int, std::string, double> t{1, "hi", 3.14};
auto [id, name, score] = t;

// if constexpr(コンパイル時分岐)
template<typename T>
std::string describe(T val) {
    if constexpr (std::is_integral_v<T>) {
        return "integer: " + std::to_string(val);
    } else if constexpr (std::is_floating_point_v<T>) {
        return "float: " + std::to_string(val);
    } else {
        return "other";
    }
}
std::optional と std::variant応用

std::optional は値があるかどうかを表す型(null 安全な代替)。std::variant は型安全な共用体。std::visit でパターンマッチ的に処理できる。

C++
#include <optional>
#include <variant>

// optional: 値なし状態を表現
std::optional<int> findFirst(const std::vector<int>& v, int key) {
    for (int x : v) if (x == key) return x;
    return std::nullopt;
}
auto r = findFirst({1,2,3}, 2);
if (r) std::cout << *r;    // 2
r.value_or(-1);            // なければ -1

// variant: 型安全な共用体
using Val = std::variant<int, double, std::string>;
Val v = 42;
v = "hello";

std::visit([](auto&& x) {
    std::cout << x << "
"; // 型に応じた処理
}, v);

if (std::holds_alternative<std::string>(v)) {
    std::cout << std::get<std::string>(v);
}
モジュールとコルーチン(C++20)応用

C++20 のモジュールはヘッダファイルをプリコンパイルするより速く、ODR 問題やマクロ漏れを防ぐ。コルーチンは co_yieldco_awaitco_return で中断/再開できる関数。

C++
// モジュール(C++20)
// math.ixx
export module math;
export int add(int a, int b) { return a + b; }

// main.cpp
import math;
int r = add(1, 2);

// コルーチン(C++23 の std::generator 利用例)
// #include <generator>
// std::generator<int> fibonacci() {
//     int a = 0, b = 1;
//     while (true) {
//         co_yield a;
//         std::tie(a, b) = std::make_tuple(b, a + b);
//     }
// }
// for (int n : fibonacci() | std::views::take(10)) {
//     std::cout << n << " "; // 0 1 1 2 3 5 8 13 21 34
// }
C++20 のコルーチンは言語キーワードのみ定義しており、std::generator は C++23 から。コルーチンを使うには cppcoro・Asio 等のサードパーティライブラリか、C++23 への移行が必要。
🏠