モダンC++はC++11以降、一連の重要なアップデートを経てきました。それぞれが、言語をより効率的で、読みやすく、保守しやすくすることを目的とした新機能と機能強化をもたらしています。以下は、C++11以降の各バージョンで導入された主要な機能の概要と、その使用法に関するコメントです:
C++11
C++11はC++言語の重要な進化を記録し、C++プログラミングを近代化・簡素化するいくつかの強力な機能を導入しました。最も影響力のある機能のいくつかを、使用例とともに紹介します:
1. auto型推論
「 auto 」キーワードにより、コンパイラは初期化子から変数の型を自動的に推論できます。
auto x = 42; // int
auto y = 3.14; // double
auto s = "hello"; // const char*
2. ラムダ式
ラムダは、コード内で直接無名関数を定義する簡潔な方法を提供します。
auto add = [](int a, int b) { return a + b; };
int result = add(3, 4); // result is 7
3. 範囲ベースforループ
配列、ベクター、その他のコンテナなどのコレクションの反復処理を簡素化します。
std::vector<int> numbers = {1, 2, 3, 4, 5};
for (int n : numbers) {
std::cout << n << " ";
}
4. スマートポインタ
「 std::unique_ptr 」と「 std::shared_ptr 」を導入し、動的メモリを自動的に管理して、メモリリークを防ぎます。
std::unique_ptr<int> ptr(new int(10));
// No need to delete ptr manually; it will be deleted when it goes out of scope
5. ムーブセマンティクス
ムーブセマンティクスは、リソースをコピーではなく転送できるようにすることで、パフォーマンスを最適化します。
std::vector<int> makeVector() {
std::vector<int> v = {1, 2, 3};
return v; // Moves v rather than copying
}
std::vector<int> v = makeVector(); // Efficient move
6. nullptr
「 NULL 」を「 nullptr 」に置き換え、ヌルポインタをより安全に表現します。
int* p = nullptr; // p is a null pointer
7. constexpr
コンパイル時定数式を可能にし、コンパイル時に計算を実行することでパフォーマンスを向上させます。
constexpr int square(int x) {
return x * x;
}
int arr[square(5)]; // Creates an array of size 25
8. std::thread
スレッドを作成・管理する標準的な方法を提供し、並行プログラミングを可能にします。
#include <thread>
#include <iostream>
void hello() {
std::cout << "Hello from thread!" << std::endl;
}
int main() {
std::thread t(hello);
t.join(); // Wait for thread to finish
return 0;
}
9. 可変引数テンプレート
可変数の引数を持つテンプレートをサポートし、テンプレートプログラミングをより柔軟にします。
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // Fold expression (C++17)
}
print(1, 2, "three", 4.0);
10. 統一初期化
変数とコンテナを初期化するための一貫した構文を可能にします。
int arr[] = {1, 2, 3};
std::vector<int> v = {1, 2, 3, 4, 5};
struct Point { int x, y; };
Point p = {1, 2};
C++14
C++14はC++11の基盤の上に構築され、コードの可読性、柔軟性、パフォーマンスを向上させるいくつかの重要な機能強化を導入しました。主要な機能を例とともに紹介します:
1. ジェネリックラムダ
C++14では、ラムダのパラメータリストで auto を使用でき、ラムダがより柔軟になり、ジェネリックコードで使いやすくなりました。
例:
auto add = [](auto a, auto b) { return a + b; };
std::cout << add(1, 2) << std::endl; // Output: 3
std::cout << add(1.5, 2.3) << std::endl; // Output: 3.8
2. 変数テンプレート
変数テンプレートにより、関数やクラスだけでなく、変数のテンプレートを定義できます。
例:
template<typename T>
constexpr T pi = T(3.1415926535897932385L);
std::cout << pi<double> << std::endl; // Output: 3.14159
std::cout << pi<float> << std::endl; // Output: 3.14159f
3. 戻り値型の推論
C++14では、関数が auto。
例:
auto add(int a, int b) {
return a + b;
}
std::cout << add(1, 2) << std::endl; // Output: 3
を使って戻り値型を自動的に推論できます。
このユーティリティ関数は std::unique_ptr インスタンスの作成を簡素化し、コードをより読みやすく、エラーが起きにくくします。
例:
#include <memory>
auto ptr = std::make_unique<int>(42);
std::cout << *ptr << std::endl; // Output: 42
5. deprecated属性
「 [[deprecated]] 」属性は、関数や変数を非推奨としてマークし、使用時にコンパイル時警告を生成するために使用できます。
例:
[[deprecated("Use new_function() instead")]]
void old_function() {}
void new_function() {}
int main() {
old_function(); // Warning: 'old_function' is deprecated: Use new_function() instead
new_function();
}
6. バイナリリテラルと桁区切り
C++14では、 0b または 0B のプレフィックス付きバイナリリテラルと、大きな数値の可読性を向上させる桁区切りとしてのシングルクォートが導入されました。
例:
int binary = 0b1010; // Binary literal
int largeNumber = 1'000'000; // Digit separator
std::cout << binary << std::endl; // Output: 10
std::cout << largeNumber << std::endl; // Output: 1000000
C++17
C++17は、言語をより表現力豊かで使いやすくする、豊富な新機能と機能強化をもたらしました。主要な追加機能のいくつかを、詳細な説明と例とともに紹介します:
1. std::optional
std::optional は、存在するかどうかわからない値を表すユーティリティです。値を返さない可能性のある関数に便利です。
例:
#include <optional>
#include <iostream>
std::optional<int> find_even_number(int num) {
if (num % 2 == 0) return num;
return std::nullopt;
}
int main() {
auto result = find_even_number(4);
if (result) {
std::cout << "Even number: " << *result << "\n";
} else {
std::cout << "Not an even number\n";
}
}
2. std::variant
std::variant は型安全な共用体で、変数が指定された複数の型のいずれかを保持できるようにします。
例:
#include <variant>
#include <iostream>
int main() {
std::variant<int, float, std::string> my_variant;
my_variant = 10;
std::cout << std::get<int>(my_variant) << "\n";
my_variant = 3.14f;
std::cout << std::get<float>(my_variant) << "\n";
my_variant = "Hello";
std::cout << std::get<std::string>(my_variant) << "\n";
}
3. std::any
std::any は、あらゆる型の単一値のための型安全なコンテナです。値の型がコンパイル時にわからない場合に便利です。
例:
#include <any>
#include <iostream>
int main() {
std::any my_any;
my_any = 42;
std::cout << std::any_cast<int>(my_any) << "\n";
my_any = std::string("Hello");
std::cout << std::any_cast<std::string>(my_any) << "\n";
}
4. 構造化バインディング
構造化バインディングにより、タプルのようなオブジェクトを直接個別の変数に展開できます。
例:
#include <tuple>
#include <iostream>
std::tuple<int, float, std::string> get_data() {
return {1, 2.3f, "test"};
}
int main() {
auto [i, f, s] = get_data();
std::cout << "i: " << i << ", f: " << f << ", s: " << s << "\n";
}
5. if constexpr
if constexpr はコンパイル時の条件付きコンパイルを可能にします。
例:
#include <iostream>
template <typename T>
void print_type_info(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integral type\n";
} else {
std::cout << "Non-integral type\n";
}
}
int main() {
print_type_info(42);
print_type_info(3.14);
}
6. 畳み込み式
畳み込み式は、パラメータパックに対する操作の簡潔な構文を提供することで、可変引数テンプレートの使用を簡素化します。
例:
#include <iostream>
template<typename... Args>
auto sum(Args... args) {
return (... + args); // fold expression
}
int main() {
std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << "\n";
}
7. ファイルシステムライブラリ
「 <filesystem> 」ライブラリは、ファイルシステムとそのコンポーネントに対する操作を実行するための機能を提供します。
例:
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path p{"example.txt"};
if (std::filesystem::exists(p)) {
std::cout << p << " exists\n";
} else {
std::cout << p << " does not exist\n";
}
}
これらの機能は総合的にC++の力と柔軟性を高め、堅牢でモダン、かつ効率的なコードを書きやすくしています。
C++20
C++20はC++の進化におけるマイルストーンであり、言語の表現力、安全性、パフォーマンスを高める多数の強力な機能をもたらしました。主要な追加機能を例とともに詳しく見てみましょう:
1. コンセプト
コンセプトは、テンプレートパラメータに制約を指定する方法を提供し、テンプレートをより読みやすく、エラーメッセージをよりわかりやすくします。
#include <concepts>
#include <iostream>
template <typename T>
concept Integral = std::is_integral_v<T>;
template <Integral T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(3, 4) << '\n'; // Works
// std::cout << add(3.0, 4.0) << '\n'; // Error: double doesn't satisfy Integral
}
2. 範囲(Ranges)
Rangesライブラリは、データのシーケンスを扱う新しい方法を導入し、コードをより読みやすく表現力豊かにします。
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
auto result = v | std::ranges::views::filter([](int n) { return n % 2 == 0; });
for (int n : result) {
std::cout << n << ' '; // Output: 2 4
}
}
3. コルーチン
コルーチンにより、非同期コードを逐次スタイルで書くことが可能になり、並行アプリケーションの開発を簡素化します。
#include <coroutine>
#include <iostream>
struct ReturnObject {
struct promise_type {
ReturnObject get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() {}
void return_void() {}
};
};
ReturnObject foo() {
std::cout << "Hello ";
co_await std::suspend_always{};
std::cout << "World\n";
}
int main() {
auto handle = foo();
handle.resume();
handle.resume();
}
4. モジュール
モジュールは、コードを構成・インポートする新しい方法を提供し、コンパイル時間とコードのカプセル化を改善します。
// my_module.ixx
export module my_module;
export int add(int a, int b) {
return a + b;
}
// main.cpp
import my_module;
#include <iostream>
int main() {
std::cout << add(3, 4) << '\n'; // Output: 7
}
5. 三方比較(スペースシップ演算子)
三方比較演算子(<=>)は、比較の実装を簡素化し、それらを扱う一貫した方法を提供します。
#include <compare>
#include <iostream>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
};
int main() {
Point p1{1, 2}, p2{2, 3};
if (p1 < p2) {
std::cout << "p1 is less than p2\n"; // Output
}
}
6. カレンダーとタイムゾーンライブラリ
このライブラリは、日付、時刻、タイムゾーンを扱うための包括的なサポートを提供します。
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
auto now = system_clock::now();
auto today = floor<days>(now);
std::cout << "Today is: " << today.time_since_epoch().count() << " days since epoch\n";
}
C++20は言語を大幅に豊かにし、幅広いプログラミングタスクに対してよりモダンで強力にしています。これらの機能は総合的にC++コードの堅牢性、可読性、保守性を向上させます。
C++23
C++23は、言語の機能、安全性、使いやすさを高めるいくつかの強力な機能を導入しています。最も重要な追加機能のいくつかを詳しく見てみましょう:
1. std::expected
std::expected は、エラーハンドリングのための新しいユーティリティで、 std::optional に似ていますが、組み込みのエラー状態管理を備えています。
例:
#include <iostream>
#include <expected>
std::expected<int, std::string> divide(int a, int b) {
if (b == 0) {
return std::unexpected("Division by zero!");
}
return a / b;
}
int main() {
auto result = divide(10, 0);
if (!result) {
std::cout << "Error: " << result.error() << '\n';
} else {
std::cout << "Result: " << *result << '\n';
}
}
2. std::mdspan
std::mdspan は多次元配列ビューで、メモリ内のデータの形状とレイアウトを記述する方法を提供します。
例:
#include <iostream>
#include <mdspan>
int main() {
int data[6] = {1, 2, 3, 4, 5, 6};
std::mdspan<int, std::extents<2, 3>> mdspan(data);
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << mdspan(i, j) << ' ';
}
std::cout << '\n';
}
}
3. リフレクション(実験的)
C++23は実験的なリフレクション機能を導入し、型とそのプロパティのコンパイル時イントロスペクションを可能にします。
例:
#include <iostream>
#include <experimental/reflect>
struct MyStruct {
int a;
double b;
void foo() {}
};
int main() {
auto type = reflexpr(MyStruct);
for (auto member : type.get_data_members()) {
std::cout << "Member name: " << member.get_name() << '\n';
}
}
4. if consteval
「 if consteval 」文により、コードがコンパイル時に評価されているかどうかをチェックできます。
例:
#include <iostream>
constexpr int factorial(int n) {
if consteval {
if (n < 0) throw "Negative input!";
}
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
std::cout << factorial(5) << '\n'; // OK
// std::cout << factorial(-1) << '\n'; // Compile-time error
}
5. [[assume]]属性
「 [[assume]] 」属性により、開発者はオプティマイザに仮定を提供でき、不変条件についてコンパイラに通知することでパフォーマンスを向上させます。
例:
#include <iostream>
int main() {
int x = 5;
[[assume(x > 0)]];
std::cout << "x is positive.\n";
}
これらの機能はC++の表現力、安全性、パフォーマンスを大幅に向上させ、モダンなソフトウェア開発の堅牢な選択肢であり続けています。しかし、あなたはすでにこれらのうちいくつを使ったことがありますか?
