javascript style guide
方針
知識のある人は既にコードレベルの情報は発信しません。
望むのは正しいコードの書き方なので、それを引き続き探します。
前回の方針とは少し方向を変えて集合知に頼ってみます。
wikipedia
やはり困ったらwikipedia。 wikipediaは良いのですが、ページが細分化されている上英語じゃないと情報が少ない事が多いので案外盲点だったりします。
JavaScript syntax - Wikipedia, the free encyclopedia
github
コードや実装で困ったらgithubですが、なかなか丁度良いものは無いものです。
今回は幸運にもベストなのがありました。
日本語版もあるので、必要に応じて参照するのが良さそうです。
javascriptの情報を拾う場所
javascriptをちゃんと学習する気になったのでメモ
方針
最新の規格等に触れているブログ等を主軸に探します。
javascript draft
等で検索
文法
ECMAScript Syntax Grammar 6th Edition / Draft
ECMAScript Language Specification ECMA-262 6th Edition – DRAFT
ブログとか人とか
- azuさん
- teramakoさん
感想
javascriptの文法とか規格とかに言及している人はあまりみかけないなーといった印象です。
やはり外部のライブラリやサービスありきの言語なのでしょうか。
ひとまず文法を理解してからライブラリも徐々に拾っていきたいです。
ninjaのwindows環境ビルド
環境
windows8.1, visual studio community, python2.7
準備
PATHに追加
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools
INCLUDEに追加
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE C:\Program Files (x86)\Windows Kits\8.1\Include\um C:\Program Files (x86)\Windows Kits\8.1\Include\shared
LIBに追加
C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86 or x64 or arm C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\lib
実行
git clone git://github.com/martine/ninja.git ninja cd ninja python configure.py --bootstrap
ninja.exeができているはずです。
関数ポインタよりもstd::functionを使おう
書きやすさ
// 関数ポインタ void (*Func)(void); void (Foo::*Func2)(void); // std::function std::function<void(void)> Func; std::function<void(Foo*)> Func2;
これは慣れてないと両方読みづらいかもしれませんね。
慣れるとfunctionの方が型名、変数名といういつものルールに従っているので分かりやすくなります。
運搬性
void Test(){} // 関数ポインタ using ReturnFunction = void (*)(); ReturnFunction Func(){ return Test; } // C++14から以下の書き方もできる auto Func(){ return Test; } // std::function std::function<void()> Func(){ return Test; }
戻り値に関数ポインタを使う場合にはusingかtypedef宣言が必要になります。
autoを使えば省略できますが以下の事に注意する必要があります。
auto Func(){ return Test; } void Func2(ReturnFunction a){} void Func2(std::function<void()> a){} int main(){ auto a = Func(); // 関数ポインタ版の方が優先して呼ばれる Func2(a); }
部分適用
これは関数ポインタでは実現できないと思います。
void Int3(int, int, int){} int main(){ std::function<void(int, int, int)> a = Int3; // 3番目の引数を3で固定した引数が2個の関数を生成する std::function<void(int, int)> b = std::bind(a, std::placeholders::_1, std::placeholders::_2, 3); }
この機能はメンバ関数ポインタと合わせると更に強力で、メンバ関数にインスタンスを拘束して通常の関数の様に扱ったりできます。
class Foo{ public: void FooFunc(){} }; int main(){ Foo foo; std::function<void()> Func = std::bind(&Foo::FooFunc, &foo); // メンバ関数を通常の関数として呼べる Func(); }
初期値
// 関数ポインタ ReturnFunction a = nullptr; if(a != nullptr){ a(); } // std::function // 明示的初期化必要無し std::function<void()> a; // operator boolが設定してあるのでチェックも容易 if(a){ a(); }
ラムダ
std::functionならラムダの運搬も可能です。
std::function<void(void)> a = []{};
ただし、キャプチャにローカルオブジェクトを含む場合不定値になるので注意が必要です。
C++の初期化は分かりにくい2
前の
newしてもゼロクリアされる訳じゃない
struct Foo{ int a; }; // デフォルト初期化、aは不定 auto foo1 = new Foo; // 値初期化、aは0 auto foo2 = new Foo();
省略可能なのでまさかと思ったら、やっぱりnewにもデフォルト初期化と値初期化がありました。
気にせずに使ってしまっていたので意識していきたいです。
C++の初期化は分かりにくい
概要
C++の初期化関係が複雑に感じたのでまとめました。
初期化の種類
まず、以下の3つの初期化が存在するのが分かりにくいです。
// デフォルト初期化 Foo foo; // 値初期化 Foo foo{}; auto foo = Foo(); // ゼロ初期化 // 常に0クリア。不定値を持たない。 static Foo foo;
デフォルト初期化
特徴としては多くの場合初期化されません。
struct Case1{ int a; }; struct Case2{ Case2(){} int a; }; struct Case3{ int a = 0; }; struct Case4{ int a; Case1 b; }; struct Case5{ int a; Case2 b; }; struct Case6{ int a; Case3 b; }; struct Case7{ Case7(){} int a; Case1 b; }; struct Case8{ Case8(){} int a; Case2 b; }; struct Case9{ Case9(){} int a; Case3 b; };
上記のクラスの中で正しく初期化される(不定値を持たない)のはCase3のみです。
値初期化
newで()を付けた場合やコンストラクタでの:を使った初期化等はこちらです。
特徴としては多くの場合0クリアされます。
Case8とCase9のみ正しく初期化されません。
Case7が正しく0クリアされるのはビックリする挙動ですよね。
規格で該当する箇所を見つける事ができなかったのでclang特有の物だった場合すみません。
初期化動作の詳細
この辺りが詳しいです。
C++ の初期化 - プログラミングの教科書を置いておくところ
C++11: Syntax and Feature
8.5 初期化子(Initializers)
memsetの問題点
以下の様なコードで対処する事が多いと思います。
Foo::Foo(){ std::memset(this, 0, sizeof(Foo)); }
これは以下の問題点があります。
- メンバが全てPODじゃないとメンバデータを壊してしまう。
- Fooクラスがstandard-layout classじゃないと不適切な領域をクリアしてしまう。
- 初期化を2回している。
結論
データしか無いクラスの初期化にはCase3と同じ形式を使いましょう。
struct Foo{ int a = 0; unsigned int b = 0; char c = 0; };
生配列よりもstd::arrayを使った方が良い理由
概要
生配列を使う人がstd::arrayを使うきっかけになれば良いなと思います。
パフォーマンス
最適化をかければstd::arrayは生配列と全く同じアセンブリを吐き出す事が知られています。
つまり、パフォーマンスに差はありません。
アルゴリズム
アルゴリズムに関しては生配列にも適応できるので問題無いでしょう。
std::array<int, 10> a; std::fill(a.begin(), a.end(), 10); int b[10]; std::fill(std::begin(b), std::end(b), 10);
forのイテレーション
イテレーションに関してはどれも割と難有りですね。
range based forが圧倒的にシンプルです。
生配列のサイズを取得するのにtemplate関数を作らなければいけないのが面倒です。
マクロは個人的にはやめた方が良いと思います。
std::array<int, 10> a; for(auto i = a.begin(), end = a.end(); i != end; ++i){} for(auto i = a.rbegin(), end = a.rend(); i != end; ++i){} for(std::size_t i = 0, size = a.size(); i < size; ++i){} for(auto item: a){} // auto Size(const T (&)[size]){ return size; } int b[10]; for(int* i = b; i != b + Size(b); ++i){} for(int* i = b + Size(b) - 1; i != b ; --i){} for(int i = 0; i < Size(b); ++i){} for(auto item: b){}
範囲外アクセスのチェック
流石にこれはstd::arrayの方が楽ですね。(なのにみんなatはあまり使わない)
範囲外アクセスがひっそりと行われるの怖くないんでしょうか。
std::array<int, 10> a; a.at(5); auto input = 5; int b[10]; if(input < 0 || Size(b) <= input){ throw std::out_of_range("faild"); }
可搬性
今でこそマシに書けますが、生配列の参照型の戻り値は一旦typedefかusingで定義してからじゃないと書けませんでした。
ポインタで扱うのはナンセンスです。サイズ分からなくなっちゃいますから。
auto Foo(std::array<int, 10> a){ return a; } auto Bar(int (&a)[10]){ return a; }
入れ替えも両方以下でいけます。
std::swap(a, b);
しかし、代入できるのはarrayだけです。
std::array<int, 10> a; a = Foo(a); int b[10]; // エラー //b = Bar(b);
比較
std::array<int, 10> a, aa; if(a == aa){} int b[10], bb[10]; if(std::equal(std::begin(b), std::end(b), std::begin(bb))){}
まとめ
生配列でも最新の書き方したらそれなりに良い感じで書ける。でもそんな苦労するくらいならstd::array使った方が楽だと思います。