C++
C++11
C++14
C++17

C++11, C++14, C++17 新機能まとめ ~使いそうなものだけ

久しぶりにC++(VC14/15, VisualStudio2015/2017)を触ることになったので、最近のC++について勉強してみた。

「使いそう」、「使うといいことあるかも」、「少なくとも読めるようにはなっておきたい」という新機能を中心に列挙しておく。

各種コンパイラの実装状況については 『コンパイラの実装状況 - cpprefjp C++日本語リファレンス』を参照。VisualStudioについての詳しい情報は『C++11/14/17 の機能のサポート (Modern C++)』を参照。

※以下、ソースコードは各参照ページより引用


C++11

よく使いそうな機能はここにまとまっている。

今すぐに使いたいC++11の新機能


nullptr

C++11 では NULL ではなく nullptr を使う


nullptrの記述パターンをいくつか挙げる

// こうではなく

setDescription(buffer, (void*)NULL);
// このように書くべき
setDescription(buffer, nullptr);

// ポインタの初期化
char* p2 = nullptr;

// 条件式
char* a;
if (a == nullptr){
:
}



統一初期化記法(Uniform Initialization)

C++11 Universal Initialization は、いつでも使うべきなのか

こんな感じでC#っぽく書ける。

int x { 3 };

int a[] { 0, 1, 2, 3 };
struct S1 { int a, b; } s { 0, 1 };
std::vector<int> v { 0, 1, 2, 3 };

pairやmapの初期化も簡潔に書ける。

pair<int, double> p { 1, 2.3 };

map<string, int> m {{"aaa", 10}, {"bbb", 20}, {"ccc", 30}};


範囲ベースの for-loop

範囲ベース for ループ


参照を使えば要素の書き換えも出来る

int ar[] = {3, 1, 4, 1, 5, 9};

for(int &x : ar) {
if( x == 1 ) // 値が1だったら
x = 123; // 123 に書き換える
}


型推測autoも使える

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

for(const auto& x : v) {
std::cout << x << "\n";
}


変更するならユニバーサル参照(auto&&)で

for(auto&& x : v) {

x += 2;
}

上の auto&& の例は『ユニバーサル参照』なので、左辺値が与えられれば x は左辺値参照に、右辺値が与えられれば x は右辺値参照になる。

C++のムーブと完全転送を知る - Fixstars Tech Blog /proc/cpuinfo

range-based for loopsの要素の型について - Qiita 最後の方の「2016/5/4 追記」に書かれている使い分けの説明がとってもいい。


右辺値参照

大データを扱うクラスを自作する時には、(可能なら)ムーブコンストラクタ・ムーブ代入演算子を定義しておくべきかも。

C++0x の右辺値参照がこんなに難しいわけがない

本当は怖くないムーブセマンティクス

C++のムーブと完全転送を知る

C++高速化 詳細な実験が素晴らしすぎる(^^)

なぜ std::vector<> は noexcept でない move constructor を呼ばないのか - Qiita 可能なら noexcept を付けておくべきという話。


ムーブコンストラクタとムーブ代入演算子

Pointer(Pointer&& f) noexcept {

this->ptr = f.ptr; // ポインタをコピー
f.ptr = nullptr; // ムーブ元はnullptrでクリア
}
Pointer& operator=(Pointer&& rhs) noexcept {
if (this == &rhs) return;
delete this->ptr; // ムーブ先のptrをdeleteしてから
this->ptr = rhs.ptr; // ポインタをコピー
rhs.ptr = nullptr; // ムーブ元はnullptrでクリア
return *this;
}


各種コンテナに追加されたemplace系メソッド

vectorなどのコンテナにユーザー定義クラスを格納する場合には、なるべくemplace系メソッドを使う。


要素を追加する例

std::vector<MyLargeClass> v;

// 従来通り push_back() を使うと、コンストラクタ+コピー(orムーブ)+デストラクタ が走る
v.push_back(MyLargeClass(1, 2));

// emplace_back()を使うと コンストラクタ しか走らないのでとても効率が良い
v.emplace_back(1, 2); // MyLargeClassのコンストラクタ引数を渡すことに注意


【C++】emplace_back/emplaceとは何か【処理効率やpush_backとの違い】| MaryCore


スマートポインタ unique_ptr<T> shared_ptr<T> weak_ptr<T>

非常に有用。でも、性質をよく理解していないと逆に危険。

C++11スマートポインタ入門


unique_ptrの使用例

// 普通に使う例

std::unique_ptr<Gdiplus::Bitmap> bitmap(Gdiplus::Bitmap::FromFile(filePath));
if (bitmap == nullptr) return false;

// 配列を確保する例 (後者はC++14で追加された make_unique()を使用する例)
std::unique_ptr<byte[]> bmpHeader(new byte[sizeofHeader]);
std::unique_ptr<byte[]> bmpHeader = std::make_unique<byte[]>(sizeofHeader);

// delete以外の解放メソッドを使う場合の例 その1
std::unique_ptr<ColorPalette, decltype(free)*> palette((ColorPalette*)malloc(paletteSize), free);

// その2 ※後述の型エイリアスを使って記述
using FILE_UPTR = std::unique_ptr<FILE, decltype(&fclose)>;
FILE_UPTR fp(_tfopen(filePath, ("wb")), fclose);



型エイリアス

typedef は C++11 ではオワコン

typedef void (*f)(int, char);    // typedefは分かりづらい

using f = void (*)(int, char); // 型エイリアスはナイス! C#っぽい。

// テンプレート化との相性もGood!

template<class Value>
using dict = std::map<std::string, Value>;


関数のdefault/delete宣言

関数のdefault/delete宣言


暗黙定義される関数を『実装はdefaultのまま』virtual/inline化することが出来たり

class X {

public:
// 暗黙定義される挙動をする、
// 仮想関数のデストラクタを定義する
virtual ~X() = default;

// 暗黙定義される挙動をする、
// インライン関数のコピーコンストラクタを定義する
inline X(const X&) = default;
};



暗黙定義される関数をdeleteすることも

class X {

public:
// コピーを禁止し、ムーブを許可する
X(const X&) = delete;
X& operator=(const X&) = delete;

// 特殊メンバ関数を明示的に定義もしくはdeleteした場合、
// それ以外の特殊メンバ関数は明示的に定義もしくはdefault宣言しなければ
// 暗黙定義されない
X(X&&) = default;
X() = default;
X& operator=(X&&) = default;
};

int main()
{
X x1;
//X x2 = x1; // コンパイルエラー!Xのコピーコンストラクタはdelete宣言されている
X x3 = X(); // OK : ムーブコンストラクトはできる

X x4;
//x4 = x1; // コンパイルエラー!Xのコピー代入演算子はdelete宣言されている

X x5;
x5 = X(); // OK : ムーブ代入はできる
}



可変個引数テンプレート(Variadic Templates)

可変値引数テンプレート(MSDN)


可変長引数を受け取るテンプレート関数

template <typename... Arguments>

int fname(Arguments... args);


具体的な例

template<typename... Arguments>

// const, &, * などの修飾も可能
void tfunc(Arguments&&... args)
{
// sizeof...で引数の個数も取得出来る
const unsigned numargs = sizeof...(Arguments);
X xobj[numargs]; // array of some previously defined type X

// args... で他の関数に渡せる
size_t size = std::snprintf(nullptr, 0, format, args...)

// std::forwardを使って完全転送
helper_func(xobj, std::forward<Arguments>(args));
}


C++のムーブと完全転送を知る


C++14


constexpr ※constant expression (定数式)

コンパイル時定数とコンパイル時処理。C++11から導入され、C++14で大幅に制限が緩和された。

しかし、VC14はまだC++11相当の機能しか実装されていないようだ。VC14/15(VS2015/2017)でも概ね実装されたようだ。

constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ

constexpr指定の変数 = コンパイル時定数


constexpr指定の変数

constexpr unsigned N = 10; // コンパイル時定数は配列のサイズやテンプレート引数として使用可能

std::array<int, N> arr = {{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }};

constexpr 指定の関数 = コンパイル時に(も)呼出可能


constexpr関数

template<typename T>

constexpr T square(T const& t) { return t * t; }

// コンパイル時定数を求めることができる
constexpr int s = square(16);
static_assert(s == 256, "");


constexpr関数の制限緩和


VC14/15(VS2015/2017)も対応済み

template < typename T >

constexpr T sqrt(T s)
{
T x = s / 2.0;
T prev = 0.0;

while (x != prev) {
prev = x;
x = (x + s / x) / 2.0;
}
return x;
}


constexpr


ラムダ式

C++14で機能強化。

C++14 のラムダ式 完全解説


コード例

// ラムダ式を受け取れる関数を定義

void ReadImage(std::string& filePath, const std::function<void(double)>& func)
{
// 画像をOpenしたとして、

// 各画素でループ
for (int y = 0; y < image.Height(); y++) {
for (int x = 0; x < image.Width(); x++) {
// 渡された func を呼び出す
func(image.GetAt(x, y));
}
}
}

// 上記関数にラムダ式を渡す
// [&] : 外部のローカル変数を参照でキャプチャする
// (double v): 各画素値がfuncの引数 v に与えられる ※auto も使える
double total(0.0); // 輝度値の積算
ReadImage("test.jpg", [&](double v) {
total += v;
});


備考: 上記は std::function でラムダを受け取っているが、template化した方が速いようなので、呼び出す回数が多い場合には要検討。


上のをtemplate化

template<typename TFunc>

void ReadImage(std::string& filePath, const TFunc& func)
{
// 中身は全く同じ
}
// 呼び出し方も全く同じ

参考: VS2017でのパフォーマンス実験結果およびソースコード main()の先頭付近参照

参考: C++11 autoはstd::functionより高速 – Hossy

参考: Maraigue風。:[C++11] std::functionとは


C++17

VisualStudio2017の場合、C++17 を有効にするには、C++プロジェクトのプロパティで C/C++ ⇒ 言語 ⇒ C++言語標準ISO C++17 Standard 以上にすると有効になる。


構造化束縛

いわゆる多値返却をそこそこ容易に実現出来るようになった :thumbsup:

std::tuple<double, int, int> CalcAveMinMax(const char* filePath )

{
// 画像ファイルを開いたとして

// average, min, max を計算したりして
double ave = 128;
int min = 0, max = 255;

// tuple に詰め込んで返す
return { ave, min, max };
}

// 受け取り側はこんな感じ
auto[ aveValue, minValue, maxValue ] = CalcAveMinMax("file");


入れ子名前空間

ナイスアップデート:thumbsup:

他言語に触れていて C++に戻って来ると、これがないのがとても苦痛に感じていた。


xxx.h

// 従来の書き方

namespace company {
namespace product {
namespace category {
void func() { xxx; }
}
}
}

// 新しい書き方
namespace company::product::category {
void func() { xxx; }
}


参考: 入れ子名前空間の定義 - cpprefjp C++日本語リファレンス


その他参考ページ

ゲーム開発者のための C++11/C++14

ゲーム開発者じゃなくてもためになる。