ぷろみん

プログラミング的な内容を扱ってます

C++の難しめな用語集

概要

先日、言いたい用語がすぐに出てこなかったので備忘録を作った。私が学習するのが遅かったり難しかったものを集めた。
その特性から多くは語らないのでググって。

Return Value Optimization (RVO)

  • Named Return Value Optimization(NRVO)

データ指向設計 (Data Oriented Design)

  • キャッシュライン

Perfect Forwarding

引数と全く同じ型を伝搬させるという説明が一番分かりやすかった。

universal reference

  • Rvalue
  • Lvalue

Template MetaProgramming(TMP)

メインの機能はconceptに代替されそう。

  • Substitution Failure Is Not An Error(SFINAE)

Type Erasure

Argument Dependent Lookup(ADL)

安易にusing namespaceしてると引っかかるやつ。

unqualified name look up

Unqualified name lookup - cppreference.com

One Definition Rule(ODR)

  • ODR違反
  • odr-use
  • リンケージ

Most vexing parse

レイアウト

  • Plain Old Data(POD) C++20からtrivial classとして扱う
  • standard-layout class
  • trivial class

Rust

Rustも同じレベルのプログラミングができるので、Rustの概念を用いてC++の概念を説明しても良いはず。

  • LifeTime
  • Ownership

規格

C++er は“合法”だとか“違法”だとか言いたくて仕方がないけれど、結局どういう意味? それより適合・適格・○○動作・○○規則・診断不要いろいろの関係が謎

ガイドライン

CppCoreGuidelines

  • Guideline Support Library (GSL)

VisualStudio2017はC++17に対応しているがデフォルトでは無効になっている

C++17を有効にする

プロジェクト > プロパティ > 構成プロパティ > C/C++ > 言語 > C++言語標準
から設定できます。

せっかくMSが標準まで追いついてくれたのに有効にしないともったいないです。

おまけ

上記を言いたかっただけなんですが、あまりに短かったのでVisualStudioで私が使う設定を書いておきます。

ツールバーにあるソリューション構成ドロップダウンリストの幅変更

デフォルト幅だとUnreal Engineのビルドをするとき等にソリューション構成名が長すぎて今何ビルドしているか分からなくなるので設定してます。
用語が限定的過ぎてググラビリティがやたら低くて毎度見つけるのに苦労します。

srz-zumix.blogspot.com

後、さっき調べて知ったんですがテキストが編集可なのがコンボボックスで編集不可なものがドロップダウンリストらしいです。
同じものを指す用語かと思ってました。

ClangFormat

コーディングスタイルを設定できます。
Visual Studioデフォルトのフォーマットが気に入らないなら変更しましょう。
独自のコーディングスタイルを選択するよりはデフォルトフォーマットを選択した方が良いとは思いますが。

ツール > オプション > テキストエディター > C/C++ > 書式設定 > 規定の書式スタイル

メンバ変数の色変更

Visual Studioのハイライトは優秀で言語内部の概念に対しても色を変えることができます。
しかしビルドに依存するのでUEぐらいの複雑なプロジェクトになるとハイスペックPCでも1分くらいハイライトが消えることがあります。

メンバ変数はコード上だと全くの宣言なしでいきなり登場するので視認性を上げると心が落ち着きます。

ツール > オプション > 環境 > フォントおよび色 > 表示項目 > C++フィールド

Chromeとかで使われているグラフィクスライブラリSkiaのWindowsビルド

手順

git clone https://github.com/google/skia

masterブランチだとWindowsに優しいものが入っていないのでtagを使います。

git checkout -b dev refs/tags/chrome/m39_2138

上記タグのコミットだとリポジトリルートにmake.batが存在するので、こちらをVisual Studio環境変数を適用した上で実行するとビルドできます。
環境変数の準備は一般的に面倒なので設定済み開発用コマンドプロンプトを利用すべきでしょう。
VSのインストールパスの下記バッチファイルを起動させたり、ウィンドウズアイコンのインストールしたアプリから呼ぶこともできます。

VisualStudio/Common7/Tools/VsDevCmd.bat

ただ1つだけ罠があって、いくつかのファイルがBOMなしutf-8で保存されているためビルドが失敗します。
私はそれらのファイルにBOMを追加することで対応しました。

最初はファイルエンコードを一致させることで解決しようと思ったのですが他のファイルにvimで:set fenc?としても
fileencoding=としか返してくれず諦めました。読み取れなかったらこうなるんですかね。

参考

qiita.com

この記事によりBOMなしutf-8が原因だと気付けました。ありがとうございます。

std::vectorのようなコピーコストの高いクラスに参照を使ってはいけないシチュエーション

概要

RVO(Return Value Optimization)の話です。特別に触れませんがNRVO(Named RVO)も最適化されるものとして話を進めています。

昔の効率の良いコード

void Initialize(std::vector<int>& buffer) {
    // bufferに色々な計算結果を詰める
    buffer.emplace_back(5);
}

昔は新しいデータ列を作成する際、アロケートの際には上記のような関数が使われていました。
それ故に下記のような分かり難い記述が散らかることになります。

std::vector<int> buffer;

// これくらいならまだ良いけど
Initialize(buffer);

// 生成に必要な引数が増えると何がなんだか
Create(buffer, elementA, elementIndex, combineBuffer);

// 使う
Use(buffer);

しかし、これには仕方ない事情がありました。
std::vectorを返すのがスマートだと分かっていても、そんな大きなものを返すとコピーコストが高いことが予想されるからです。

今の効率の良いコード

// 引数によってbufferが生成されていることがはっきり分かる
auto buffer = Create(elementA, elementIndex, combineBuffer);
// 使う
Use(buffer);

ここでRVOを知らない人はbufferの型がstd::vectorというのを見てびっくりします。
関数を見てみましょう。

std::vector<int> Create(...) {
    std::vector<int> result;
    // 色々な計算結果を詰める
    result.emplace_back(5);

    return result;
}

確かにRVOが効かなかった昔のコンパイラならば最悪のコードです。膨大なバッファのコピーが無意味に走る。
しかし、今の私たちにはRVOがあります。
上記コードは以下のコードと同等と解釈されます。

std::vector<int> buffer;

// 色々な計算結果を詰める
buffer.emplace_back(5);

// 使う
Use(buffer);

つまり、バッファポインタのコピーコストや関数の呼び出しコストすらも無く同様の処理を記述できるようになったのです。
昔のコードよりも高速に処理される上に遥かに高い可読性を持つこの初期化方法は想像以上に多くの可能性を私たちに提供します。

まず、コピーコストを無視できるので何らかのクラスを生成している箇所を機械的に分離できるようになります。
長い関数は大抵途中でなんらかのデータ列を生成していることが多いので、その部分だけでも分離することで効率を落とさずに可読性を上げることができます。

もうすでにRVOを行わないコンパイラは少ないと思っています。
加えて、最近の規格ではRVOを保証するものも出て来ているので上記のような書き方をしない理由はないと思います。

3Dプログラマのための数学

概要

画像付きで詳細に解説しようとしたらモチベーションが足らなかったので下書きをブログで。
Qiitaとかだと数学記号使えるので恰好良く書ける反面書くの面倒。

内積

3Dプログラミングで一番出てくるやつ。
何故大人気かというと三角関数の中で一番高速で計算できるから。

内積

float DotProduct(const float3& a, const float3& b) {
    return a.x * b.x + a.y * b.y + a.z * b.z;
}

のように足し算と掛け算のみで計算できるので速い。
ちなみに引き算は問題ないが割り算は他の演算と比べるとちょっと遅い。
それでもcos関数とかsqrt関数と比べるとマシ。
一般的な認識はそんな所だと思うけど、最近のGPUやCPUは進化しているのでnvidiaの出している情報を見ると、この関数は1命令でいける、これは4命令かかるとかが書いてあったりするので決まった環境での最適化はもうかなり特化した人じゃないと難しそうです。
まー、今はモバイルも強いので上記の一般常識に従う方向でも良いでしょう。

話がそれました。
内積

DotProduct(a, b) = aベクトルの長さ * bベクトルの長さ * cos(aベクトルとbベクトルの作る角度);

とも表すことができるので、aベクトルとbベクトルが単位ベクトル(長さが1のベクトル)の場合aとbベクトルのcosが取得できます。
またaベクトルのbベクトル成分が欲しい場合はaベクトルとbの単位ベクトルの内積をとってやればaの長さ * cosとなり取得できます。
この操作は射影(Projection)とも呼ばれaベクトルからbベクトルに垂線を引いた時の長さに相当します。
3Dでこの情報が欲しい場合の例としては法線ベクトルや接線ベクトル成分を取得したい場合等があります。

長さを求めるのは大変

長さを求めるためにはsqrt関数を使わないといけません。これは他の演算と比べると重いので使いたくありません。
なので長さの2乗を使います。長さの2乗は

return x * x + y * y + z * z;

で取得できるので高速です。
長さの2乗を計算する関数が用意されていない環境ではDotProduct(a, a)のように内積で代用したりします。

ベクトルは長さをもっている

ベクトルaを考えるとき、aベクトルは

aベクトルの長さ * aベクトルの単位ベクトル

と考えることもできます。

ここから一切の特殊な演算を使わずにaベクトルのbベクトル成分ベクトルを求めることができます。

aベクトル * DotProduct(a, b) / DotProduct(a, a)

上記演算の式を整理すると

aの単位ベクトル * bの長さ * cos

となり、aとbの長さを求めていないのにaとbの長さが必要な演算ができてしまうのです。
単位ベクトルを求めるためにはaベクトル / aベクトルの長さを計算する必要がありますからね。

外積

外積は計算上は

return float3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);

これはやはり引き算と掛け算のみなので高速です。
しかし、演算結果にベクトルを含んでしまうので扱いにくいです。
意味としては

aの長さ * bの長さ * sin(aとbの作る角) * aとbが作る面の法線単位ベクトル

で表されます。 sinとcosは相反するほぼ同じ意味を持つので、扱いにくい外積よりも内積を使うというわけですね。

最後に

はい、「3Dプログラマのための数学」なんて言っておきながら足し算と引き算と掛け算しかしませんでした。

数学関数を高速動作させるやべーやつも研究されていますが、内積で解決できる問題は内積で解決するのが一番速いです。 github.com

C++コーディングガイドライン哲学1

概要

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md
の「P: Philosophy」の感想や補足です。

アイディアを直接コードで表現する

例としてStrong typeやfor文の代わりにアルゴリズムを使うことを提案しています。
また、constを一貫して使うことやキャストによる型システム破壊の防止について言及しています。

Strong type

プリミティブ型のような汎用性の高い型は間違いの元なので、間違った操作ができない型を使おうという発想です。

IndexA indexA(10);
IndexB indexB(5);
A a;
B b;
a[indexA]; // OK
a[indexB]; // コンパイルエラー
if(indexA == IndexA(10) && indexB == IndexB(5){} // OK
if(indexA == 5 && indexB == 10){} // コンパイルエラー

Indexが全部int型だと必ずミスをします。自分は良くても後で保守する人は必ずミスをします。

スタンダードライブラリにある機能を再発明しない

これは簡単なようでいてかなり難しいです。これを正しく運用するためにはスタンダードライブラリの全機能を把握する必要があるからです。無理です。
なので、よくある処理を書く前にライブラリにないかな?といったんググるだけで良いと思います。
良く使いそうな例を少し挙げておきます。

一般的にあって欲しいfilterとmap(LinqだとWhereとSelect)もあるといえばあるけど・・・状態なので結局作らなければいけないことも多いです。
boostまで考慮に入れるともう少し便利になります。

  • std::remove_ifとcontainer::erase
    条件を満たした要素の配列を取得できます。
    参照配列を弄って欲しくない時はコピーするしかない?

  • std::transform 指定した方法で変換された配列を取得します。
    変換後結果を詰めた配列を返すものということは知っておいた方が良いでしょう。 Boost.Rangeをパイプライン記法のままコンテナに変換 - Qiita

for文でこの処理を書くとかなり蛇足感が出ます。

  • std::all_of 全ての要素が条件を満たすか確認します。

  • std::any_of どれか1つでも条件を満たす要素があるか確認します。

algorithmぐらい頻繁に使うがnumericにあるので存在を忘れやすい

  • std::accumulate 総和を求める

constを一貫して使う

constは伝搬されるものなので最初から心がけていないと「あれ?この関数にconst変数渡せないぞ」となってしまい、constを使わない文化が蔓延してしまうので早めに取り組んだ方が良いでしょう。
constに気をつける箇所はたくさんあります。

class Input {
public:
  // メンバ関数のconst
  Arguments GetArguments() const {
    return arguments;
  }

private:
...
  // メンバ変数のconst
  // コンストラクタで初期化しないとエラーとなるので使わない人も多いですが、const性を持つのなら定義すべきです
  const int version;
};

// 引数のconst
void Function(const Input& input) {
  // ローカル変数のconst
  // 蛇足っぽく見えるので嫌う人も多いですがconstを常に使う方が哲学に沿います
  const auto& arguments = input.GetArguments();
}

キャストした際にはそれがキャストされた値だと明示する

おそらくstd::anyやstd::variantについて言及しているのだと思います。
別の型として汎用管理しても良いけど元の型を忘れちゃダメ。

ユーザ定義リテラル

例として下記のようなコードが提示されています。

change_speed(23m / 10s);  // meters per second

何の説明もなく出現していますが、これは疑似コードではなくユーザ定義リテラルです。
ユーザー定義リテラル - cpprefjp C++日本語リファレンス

なにかと評判の良くないユーザ定義リテラルですが、ガイドラインで利用されていることを考えると使っていった方が良いのでしょうね。

ISO Standard C++を使う

理由は移植性だと思うのですが、このガイドラインはISOのために書かれているからISO Standard C++を使おうってなってます。
移植性では納得しない人が多かったのでしょうか。
まー、守ることは環境依存な拡張機能は使わないようにしようってことです。

意図を表現しよう

例えば配列の各要素について処理したい場合はwhileではなくforを使おうということです。
上記ぐらいは多くのプログラマーが実施できているとは思いますが、Read Writeの意図を明示できている人は思っているよりも少ないです。

// 参照のみすることを明示する
for (const auto& x : v) {}
// 書き換えを行うことを明示する
for (auto& x : v) {}

また、プリミティブ型は曖昧になりがちなので意味を表現した型を使うことを勧めています。

// 各パラメータが何を指しているのか分かり辛い
draw_line(int, int, int, int);
// 分かりやすい
draw_line(Point, Point);

最後により良い互換機能が提供されているものについてはそちらを使いましょう。

  • 単純なforはrage baseのforに
  • ポインタとその長さの組み合わせによる範囲指定はspanに spanとはGuideline Support Library(gsl)に定義されるクラス。MSによる実装が下記
    GSL/span at master · Microsoft/GSL · GitHub
  • あまりに長いスコープ変数 ネストが深くなったりスコープが長くなり過ぎる場合、意味単位に分割してクラス化や関数化を行い、スコープはせめて1画面以内にするのが望ましいと私は思います。
  • 生のnewやdeleteはスマートポインタやmake_shared、make_uniqueに
  • 関数の引数にプリミティブ型が多い場合は意味を表現した型を使うように

理想的には、プログラムは静的型安全であるべき

クラッシュやセキュリティの脅威となる既存の方法への代替案として下記を提案しています。

  • union(共用体)の代わりにstd::variant
  • キャストの代わりにtemplate
  • 部分配列の利用にポインタやインデックスを使うのではなくgsl::span
  • 範囲外アクセスエラーもgsl::spanなら検出できる
  • キャストするとしてもgsl::narrowやgsl::narrow_castを使おう narrowはnarrow<IndexA, IndexB>でIndexAをIndexBにstatic_castした値が入っているよと明示するクラス

実行時チェックよりもコンパイル時チェックの方が良い

間違ったコードはコンパイル時にエラーを出し、決して実行時までエラーを遅延させてはいけない。
また、static_assert等で検証した事項を実行時に再検証してはいけない。

コンパイル時にチェックできないものは、実行時にチェックできなければならない

上記タイトルだと幅広そうですけど、ようするにレンジアクセスにポインタとインデックスは使わずにgsl::span使おうぜって内容です。

実行時エラーは早めに捕捉しよう

nullポインターに代表される多重チェックをしないためにどうするべきか。

nullチェックが必要なポインタの代わりに参照を使うように、境界値チェックが必要な配列とレンジを指定する代わりにspanを使うようにチェックが終わって安心して使えることを明示する型を利用するようにしましょう。ということだと思います。次の項目で出てくるRAII的な考え方をエラーチェックにも適用しようということでしょう。

おわりに

これで哲学の項目の半分くらいです。続きは気が向いたら書きます。

好きなC++ランキング

好きなだけで使えるとは言っていない所がポイントです。
コンパイラだけでなく、周辺環境も含めて考慮しているのでC++としています。

1位 wandbox + clang/gcc

個人的に新機能を試す時が一番幸福度が高いのでこの組み合わせが好きです。
環境構築が必要なしに新機能を試せるのは本当にありがたいです。好き。
立ち上がる速度、実行までの速度、速度でいえばこれに勝てる構成はないです。好き。

ただ、ファイル数が多かったり、自分の好きなエディタと連携しようと思うと面倒だったりは良くないです。Webサービスである以上は仕方ないのですが。
また、私が愛用しているchromeプラグインVimiumとも相性が悪いです。対象ページでプラグインをオフにするとページ切り替えもたつくし、オンにしていると不意にページを閉じてしまったりで。
後、boostがいまいち使いにくい印象を受けます。コンパイルが必要なライブラリ周りがどうやったら試せるのか分からなかったのかな?
以前、boost/qvmの機能を試そうとした時に上手に解決できませんでした。

2位 Cloud9 + clang/gcc

今はカード登録が必要になった?みたいですが、私は昔っから使っているお陰か登録していないので気軽です。
ようするただのVM環境なのでLinuxでいーじゃん的な意見はもっともなのですが、新機能や新しいことを試す時に一番重視したいのは環境の再構築しやすさなので私的には外せないポイントだったりします。クリーンなLinuxマシーンが1分かからずに作れて、何個でも作って良いとかうおおーってなります。好き。

こちらもテキストエディタ関係でwandboxと同じ悩みを抱えていますが、純粋なvimを使えるのでマシです。ただ、vimで日本語を扱うと表示がバグります。日本語を扱う場合は素直に備え付けのエディタを使わなければなりません。うーむ。
後、私は職業柄GUI関係のコードの実験を行いたいことが多いのですが、そちらの実験ができません。これは仕方ない。
また、無料でつかえるマシンのメモリや処理速度が貧弱なので一部ビルドが辛いです。Railsのinstallにそのままだとコケたりします。無料だとしばらくアクセスしなかったVMにアクセスするのに時間がかかるのも面倒です。しかし、ここらへんはお金で解決できる問題なので仕方ないです。私も使用頻度がもう少し高ければ払っていきたいです。

3位 JetBrains + clang/gcc/VC

この組み合わせは私がヘビーに試せていないだけで1位になるポテンシャルを秘めていると思っています。
私がしっかりと試したのはAndroidStudio + gccだけなのでその知見で書きます。CLionは触るだけしかしていないので、最初のセッティングでvimキーバインドを選択できたのが感動した程度しか言えないです。

上記2つと比べるとマシンに入れる必要があるので起動時間、アップデート時間、環境汚染等は避けられません。しかし、それを補って余りあるメリットを提供してくれます。
素晴らしいと思うのは、CMake前提のプロジェクト構成をデフォルトで吐き出してくれることです。これならCMakeやMakeの知識がない人でもC++の簡単なプロジェクト管理ができます。しかもツール、環境間の相互運用性も高い。グッジョブですね。
また、テキストエディタvimキーバインドの相性も良く特に不満なく使えます。上記2つとは比べものにならない自動補間、自動問題解決を行ってくれます。

最大の問題点。AndroidStudioでのC++Android向けアーキテクチャに対してコンパイルされるので汎用性が低い。CLionは期間限定で無料試用はできるものの有料。
個人的には開発環境が有料というのはちょっと無いので、せめて個人利用の範囲では無料にしてくれるまで押すつもりはないです。個人でがっつり使ってないものを企業で推進するのは難しいと思いますよっと。

4位 VM + clang/gcc

最近のマシンは高性能になってきているのでVMを常時起動してても重さは感じないので素晴らしいですね。
この構成もLinuxで良いじゃねーか案件なんですが、私が常用しているPCはWindowsだし、上記で触れた環境の壊しやすさを考慮するとこの選択になります。
VMには昔はVMwareを使っていたんですが、最近ではVirtualBoxVagrantで運用していました。

Web環境の際に不満だったGUIの実験ができ、環境的にもただのLinuxなので完璧かのようにみえます。

しかし、やはりVM関係の特定の操作には時間がかかる、複数のVMを管理しているとストレージを圧迫するし簡単に管理できる何かが必要という感じになりました。
それにVM上でIDEまで入れるのなら流石にLinux機で良いと思うので立ち位置が微妙な構成に感じます。
また、これが私にとって一番致命的だった理由なのですが、VMの操作を色々行った後はVirtual Network Bridgeが生成され、それがネトゲPingに深刻な影響を与えることが時々発生したためです。開発のために日常を犠牲にするなんてあってはならないことです。

5位 VisualStudio + VC

はい、仕事ではもっぱらこれです。

vimキーバインドプラグインによりほぼ不満なく動作します。その上優秀な補完、定義へのジャンプ、検索、デバッグと至れり尽くせりです。
昔は絶対に使いたくなかったVCですが、VisualStudio2015辺りに付随するVCからしっかりと新しいC++の機能に対応してきてかなり良くなってきています。
また、NuGetによるライブラリ連携も簡単にできるようになっており、テストやboost、OpenCV等を簡単に導入できるもの魅力的です。

しかし、他の構成では扱わない規模のプロジェクトを扱っているので公正な判断とは言いづらいですが、重い、固まるのが最大のデメリットです。
補完や定義へのジャンプを要求したら数十秒返事が返ってこないのが頻発します。やってらんねぇ。
また、ソリューションファイルが扱い辛いです。特定のパスを要求しようと思ったらソリューションファイルに直書きするしかないとかなるんなら、初めから設定ファイルを書けるプロジェクトを生成して欲しい。CMake等でソリューションファイルを生成した方が分かりやすく想定した動作をします。

6位 MinGW (or MinGW-W64), Cygwin + gcc/clang

昔はお世話になったこの構成ですが、VMも走らせることができる今となっては選択しなくても良いのかなと思います。
IDEが内部でこれらを処理してくれるので、自分で触る必要はないと思います。

ただ、MinGWのコマンドは便利なのでC++コンパイル環境以外としてなら使うべきだと思います。
私はgit bash(gitに最適化されたMinGW環境)でそれらを代用しています。

おまけ vcpkg

GitHub - Microsoft/vcpkg: VC++ Packaging Tool

NuGetの代わりになるかと思いきや管理が大変過ぎて使えない感じになってしまっている惜しいプロジェクト。
各cプロジェクトを依存関係を考慮してビルド、リンクできるようにするパッケージマネージャ。

それだけ聞くと最高な感じだが、実状として各プロジェクトの依存関係やビルドファイルを全部人力で構築しているので対応が遅れるのとバグが取り切れてない。
誰でも編集できるようにすると質の低いビルドファイルが作られるという理由で中央集権化した(NuGetが低質なビルドファイルにより壊れているパッケージが多数あることを受けてかな?)結果役に立たないものになってしまった感じ。

やはり、やるべきは良質なテンプレートの提供とデベロッパーの啓蒙なのだろう。

おまけ Unreal C++

Unreal Engineをビルドする際に使用される特殊加工されたビルド環境です。

Reflectionが使えないC++に無理矢理ではあるもののスマートにReflectionをもたらした事は素晴らしいと思います。関数名やクラス名をそのままエディタ上で扱えることは苦労以上のメリットがあると考えます。
また、モジュールの概念が素晴らしく、これにより明確なスコープの管理と公開範囲の管理、ホットリロード等が実現されます。

このシステムについての不満としてはプリフィックスの強制と使っているコーディング規約、膨大なファイル量による重さがあります。

最後に

では、みなさん。良きC++を。