LoginSignup
234

More than 3 years have passed since last update.

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

Last updated at Posted at 2016-10-28

久しぶりにC++(VC14/15, VisualStudio2015/2017)を触ることになったので、最近のC++について勉強してみた。
「使いそう」、「使うといいことあるかも」、「少なくとも読めるようにはなっておきたい」という新機能を中心に列挙しておく。

各種コンパイラの実装状況については 『コンパイラの実装状況 - cpprefjp C++日本語リファレンス』が詳しい。VS2019の実装状況までちゃんと書かれている。感謝 :pray:

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

C++11

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

nullptr

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

nullptrの記述パターンをいくつか挙げる
// こうではなく
SetDescription(buffer, (void*)NULL);
// このように書くべき
SetDescription(buffer, nullptr);

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

// 条件式
if (p == 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 numOfArgs = sizeof...(Arguments);

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

    // std::forwardを使って完全転送
    helper_func(p, 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/2019の場合、C++17 を有効にするには、C++プロジェクトのプロパティで C/C++ ⇒ 言語 ⇒ C++言語標準ISO C++17 Standard 以上にする。

構造化束縛

いわゆる多値返却をそこそこ容易に実現出来るようになった :thumbsup::thumbsup::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:

xxx.h
// 従来の書き方
namespace company {
    namespace product {
        namespace category {
            void func() { xxx; }
        }
    } 
}

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

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

その他参考ページ

ゲーム開発者のための C++11/C++14
ゲーム開発者じゃなくてもためになる。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
234