C++: C++モダン機能
最終更新:2026-08-31
レッスン47では、テンプレートメタプログラミングについて学びました。
ここでは、C++のモダン機能について学びます。
C++11/17/20/23の新機能を使いこなすことは、現代C++開発者の必須スキルです。
1. C++進化の歴史
(1) 1.1 C++標準の変遷
| バージョン | 年 | 重要な新機能 |
|---|---|---|
| C++11 | 2011 | ラムダ、auto、スマートポインタ、constexpr |
| C++14 | 2014 | 汎用ラムダ、std::make_unique |
| C++17 | 2017 | std::optional、std::variant、畳み込み式 |
| C++20 | 2020 | コンセプト、Ranges、コルーチン |
| C++23 | 2023 | std::expected、Deducing This |
(2) 1.2 なぜモダンC++を学ぶ?
| 利点 | 説明 |
|---|---|
| パフォーマンス | ムーブセマンティクス、constexpr |
| 安全性 | スマートポインタ、std::optional |
| 可読性 | auto、ラムダ、Range-based for |
| 生産性 | std::format、構造化束縛 |
2. auto
(1) 2.1 基本的な使い方
▶ サンプル 1:autoの使用(難易度 ⭐)
CPP
#include <iostream>
#include <vector>
int main() {
auto x = 42; // int
auto y = 3.14; // double
auto s = "hello"; // const char*
auto v = std::vector<int>{1, 2, 3};
for (auto& elem : v) {
std::cout << elem << std::endl;
}
return 0;
}
(2) 2.2 autoの応用
| 用法 | 説明 |
|---|---|
auto |
型推論 |
auto& |
参照 |
const auto& |
const参照 |
auto&& |
ユニバーサル参照 |
3. ラムダ式
(1) 3.1 基本的な使い方
▶ サンプル:ラムダの使用(難易度 ⭐)
CPP
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// ラムダで出力
std::for_each(v.begin(), v.end(), [](int x) {
std::cout << x << " ";
});
std::cout << std::endl;
return 0;
}
(2) 3.2 キャプチャ
| キャプチャ | 説明 |
|---|---|
[=] |
すべての変数を値でキャプチャ |
[&] |
すべての変数を参照でキャプチャ |
[x] |
xを値でキャプチャ |
[&x] |
xを参照でキャプチャ |
[this] |
thisポインタをキャプチャ |
4. std::optional
(1) 4.1 基本的な使い方
問題: 「値がない」状態をどう表現するか?
解決策: std::optional(C++17)
▶ サンプル:optionalの使用(難易度 ⭐⭐)
TEXT
📖 参照専用
#include <iostream>
#include <optional>
std::optional<int> findValue(const std::vector<int>& v, int target) {
for (int x : v) {
if (x == target) {
return x;
}
}
return std::nullopt; // 値なし
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
auto result = findValue(v, 3);
if (result) {
std::cout << "見つかりました:" << *result << std::endl;
} else {
std::cout << "見つかりません" << std::endl;
}
return 0;
}
5. std::variant
(1) 5.1 基本的な使い方
問題: 複数の型を格納できる変数が必要?
解決策: std::variant(C++17)——型安全なunion
▶ サンプル:variantの使用(難易度 ⭐⭐)
CPP
#include <iostream>
#include <variant>
#include <string>
int main() {
std::variant<int, double, std::string> v;
v = 42;
std::cout << std::get<int>(v) << std::endl;
v = 3.14;
std::cout << std::get<double>(v) << std::endl;
v = "hello";
std::cout << std::get<std::string>(v) << std::endl;
return 0;
}
(2) 5.2 std::visit
CPP
#include <iostream>
#include <variant>
struct Visitor {
void operator()(int i) { std::cout << "int: " << i << std::endl; }
void operator()(double d) { std::cout << "double: " << d << std::endl; }
void operator()(const std::string& s) { std::cout << "string: " << s << std::endl; }
};
int main() {
std::variant<int, double, std::string> v = "hello";
std::visit(Visitor{}, v);
return 0;
}
6. std::string_view
(1) 6.1 基本的な使い方
問題: 文字列をコピーせずに参照したい?
解決策: std::string_view(C++17)——文字列への軽量なビュー
▶ サンプル:string_viewの使用(難易度 ⭐)
CPP
#include <iostream>
#include <string>
#include <string_view>
void printString(std::string_view sv) {
std::cout << sv << std::endl;
}
int main() {
std::string s = "Hello, World!";
printString(s); // std::stringから
printString("Hello"); // const char*から
return 0;
}
💡 ヒント:
std::string_viewはコピーしないため高速- ただし、元の文字列が有効である必要がある
7. コンセプト
(1) 7.1 基本的な使い方
問題: テンプレートパラメータに制約を設けたい?
解決策: コンセプト(C++20)
▶ サンプル:コンセプトの使用(難易度 ⭐⭐)
TEXT
📖 参照専用
#include <iostream>
#include <concepts>
// Numeric型のみ受け取る
template<typename T>
requires std::numeric_limits<T>::is_integer
void printNumber(T value) {
std::cout << value << std::endl;
}
int main() {
printNumber(42); // OK
printNumber(3.14); // OK
// printNumber("hello"); // エラー!
return 0;
}
(2) 7.2 標準コンセプト
| コンセプト | 説明 |
|---|---|
std::integral |
整数型 |
std::floating_point |
浮動小数点型 |
std::same_as |
同じ型 |
std::derived_from |
派生関係 |
std::convertible_to |
変換可能 |
8. 総合例
▶ サンプル 1:モダンC++の機能統合(難易度 ⭐⭐⭐)
CPP
#include <iostream>
#include <vector>
#include <algorithm>
#include <optional>
#include <string_view>
// モダンC++の機能を統合した関数
auto findInVector(const std::vector<int>& v, int target) -> std::optional<size_t> {
auto it = std::find(v.begin(), v.end(), target);
if (it != v.end()) {
return std::distance(v.begin(), it);
}
return std::nullopt;
}
int main() {
std::vector v = {1, 2, 3, 4, 5}; // CTAD(C++17)
auto index = findInVector(v, 3);
if (index) {
std::cout << "インデックス:" << *index << std::endl;
}
return 0;
}
▶ サンプル 2:構造化束縛(難易度 ⭐⭐)
CPP
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> scores = {
{"Alice", 90},
{"Bob", 85}
};
// 構造化束縛でkeyとvalueを取り出す
for (const auto& [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
return 0;
}
出力:
TEXT 📖 参照専用Alice: 90 Bob: 85
▶ サンプル 3:std::optionalの実践(難易度 ⭐⭐)
CPP
#include <iostream>
#include <optional>
#include <string>
std::optional<std::string> findUser(int id) {
if (id == 1) {
return "Alice";
} else if (id == 2) {
return "Bob";
}
return std::nullopt;
}
int main() {
auto user1 = findUser(1);
auto user3 = findUser(3);
if (user1) {
std::cout << "ユーザー1: " << *user1 << std::endl;
} else {
std::cout << "ユーザー1が見つかりません" << std::endl;
}
std::cout << "ユーザー3: " << user3.value_or("不明") << std::endl;
return 0;
}
出力:
TEXT 📖 参照専用ユーザー1: Alice ユーザー3: 不明
❓ よくある質問
Q:autoを使いすぎると可読性が下がる? A: はい。型が明確でない場合、明示的に書く方が良いです。
Q:std::optionalとポインタの使い分けは? A:
std::optionalは値がない状態を表現、ポインタはオブジェクトへの参照を表現。
Q:コンセプトは難しい? A:基本的な使い方は簡単。標準コンセプトを使うだけでも十分役立ちます。
📖 まとめ
| トピック | 要点 |
|---|---|
| auto | 型推論 |
| ラムダ | 無名関数、キャプチャ |
| std::optional | 「値がない」状態を表現 |
| std::variant | 型安全なunion |
| std::string_view | 文字列への軽量なビュー |
| コンセプト | テンプレート制約 |
📝 練習問題
-
初級(難易度 ⭐):
std::optionalを使って、辞書(map)からキーを検索し、見つかったら値を返す関数を書く。 -
中級(難易度 ⭐⭐):
std::variantとstd::visitを使って、複数の型(int、double、string)を格納・処理する関数を書く。 -
上級(難易度 ⭐⭐⭐): C++20コンセプトを使って、「加算可能」な型のみを受け取る関数テンプレートを書く(
requires句を使用)。
- C++11:auto、ラムダ、スマートポインタ、move semantics
- C++17:optional、variant、string_view、構造化束縛
- C++20:コンセプト、Ranges、コルーチン
- C++23:expected、Deducing This
次のレッスン:パフォーマンス最適化(#49)