久しぶりにC++(VC14/15, VisualStudio2015/2017)を触ることになったので、最近のC++について勉強してみた。
「使いそう」、「使うといいことあるかも」、「少なくとも読めるようにはなっておきたい」という新機能を中心に列挙しておく。
各種コンパイラの実装状況については 『コンパイラの実装状況 - cpprefjp C++日本語リファレンス』が詳しい。VS2019の実装状況までちゃんと書かれている。感謝
※以下、ソースコードは各参照ページより引用
C++11
nullptr
C++11 では NULL ではなく 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
int ar[] = {3, 1, 4, 1, 5, 9};
for(int &x : ar) {
if( x == 1 ) // 値が1だったら
x = 123; // 123 に書き換える
}
std::vector<int> v{1, 2, 3, 4, 5, 6, 7};
for(const auto& x : v) {
std::cout << x << "\n";
}
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スマートポインタ入門
// 普通に使う例
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 void (*f)(int, char); // typedefは分かりづらい
using f = void (*)(int, char); // 型エイリアスはナイス! C#っぽい。
// テンプレート化との相性もGood!
template<class Value>
using dict = std::map<std::string, Value>;
関数のdefault/delete宣言
class X {
public:
// 暗黙定義される挙動をする、
// 仮想関数のデストラクタを定義する
virtual ~X() = default;
// 暗黙定義される挙動をする、
// インライン関数のコピーコンストラクタを定義する
inline X(const X&) = default;
};
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)
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++14
constexpr ※constant expression (定数式)
コンパイル時定数とコンパイル時処理。C++11から導入され、C++14で大幅に制限が緩和された。
しかし、VC14はまだC++11相当の機能しか実装されていないようだ。VC14/15(VS2015/2017)でも概ね実装されたようだ。
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
constexpr指定の変数 = コンパイル時定数
constexpr unsigned N = 10; // コンパイル時定数は配列のサイズやテンプレート引数として使用可能
std::array<int, N> arr = {{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }};
constexpr 指定の関数 = コンパイル時に(も)呼出可能
template<typename T>
constexpr T square(T const& t) { return t * t; }
// コンパイル時定数を求めることができる
constexpr int s = square(16);
static_assert(s == 256, "");
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;
}
ラムダ式
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<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 以上にする。
構造化束縛
いわゆる多値返却をそこそこ容易に実現出来るようになった
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");
入れ子名前空間
インデントが無駄に深くならないようになった。ナイスアップデート
// 従来の書き方
namespace company {
namespace product {
namespace category {
void func() { xxx; }
}
}
}
// 新しい書き方
namespace company::product::category {
void func() { xxx; }
}
参考: 入れ子名前空間の定義 - cpprefjp C++日本語リファレンス
その他参考ページ
ゲーム開発者のための C++11/C++14
ゲーム開発者じゃなくてもためになる。