こんにちは。11月に入ったら急に頭痛が復活してきてしまったRockinWoolです。去年くらいから冬服のようなピッチリとして重い服を着ると、肩こりが悪化して頭痛が発生する気がしています。同じような症状の人がいたら嬉しいですが、この症状がある限りスーツを着て仕事するのは無理なんじゃないかと思ってしまいます。まあ、それ以前に職場で壊したメンタルを治すところからなんですが。
さて、今回のテーマはC++で詰んだポイントの備忘録まとめです。前回の記事で紹介したpybind11を使ってpythonからc++で作成したバイナリを叩けるようになってから、ずーっとc++でコーディングしてました。これが結構楽しいけれど、反面覚えることが多くてpythonほど簡単には書けない・・・。そんなこんなで備忘録を残していつでも振り返られるようにしておこうと思います。

乱数生成

まず最初にびっくりしたのがc++での乱数生成のクセの強さ。pythonだとrandom.randn(min,max)一行で書けるような内容がc++だとそうもいかない。結局自作でrandnに相当するものを作成することになりました。

int generateRandomNumber(int min, int max) {
    // 乱数生成器を作成
    std::random_device rd;
    std::mt19937 gen(rd());

    // 分布を作成し、指定した範囲の乱数を生成
    std::uniform_int_distribution<int> distribution(min, max-1);

    // 乱数を生成して返す
    return distribution(gen);
}

この関数を作成した際に引っかかったのはstd::uniform_int_distribution distribution(min, max-1);のmax-1の部分。実はpythonのrangeとかと違って、min≦乱数≦maxと出てきてしまうのです。したがってmax-1としないと挙動が異なってしまいます。というか、c++でもforループを組むときはfor(int i=0; i<N; i++)みたいに0~N-1の範囲で回すのに、なんで乱数を生成する関数だけ仕様が違うんだ・・。ということで、範囲外アクセスを生み出して一敗しました。チキショー。

配列(Vector)

c++の配列であるvectorは、慣れれば結構使いやすいと感じています。こいつのすごいところはpybind11経由でpythonから呼ばれた際に、c++側関数の返り値をvector<int>やvector<vector<int>>としてもpython側からlist型として認識されるところ!(これってどっちかというとpythonがすごいんだけど)。逆にc++で自作の構造体をstructで作って、それを返り値としてpythonに渡そうとしたら失敗しました。やっぱり1関数1機能の規則に則って一度に複数個の返り値を返さないことが大切ですね。
ちょっとクセが強いなと思ったのは、初期化時の表現。例えば下記のようなプログラムの場合、jam_counterはN個の要素すべてが0で初期化された一次元配列になります。

vector<int> jam_counter(N,0);

要素の呼び出し方はCやPythonと同じようにjam_counter[0]といった感じになります。その他、vectorで怖いなと思ったのはpush_back関数。

declared_loc.push_back(temp_loc[i]);

上記はvector<int>型のdeclared_locに要素を”追加”している命令。配列の長さが後から変わるって結構怖くないか・・?ってC経験者だと感じてしまいます。Pythonのlist型にもappendとかあったけど、あれはメモリ管理とかまったく気にしなくても良かったので安心して使ってましたけど、c++のvectorもあれと同じくらい信用してもいいのかな〜。

連想配列(map,unordered_map)

vector<int>がlist型に対応しているのに対して、こちらはdictionary型に似ていると感じました。デフォルトのmapだとKeyの順序まで覚えるらしいけど、ほとんどの場合はそこまで記憶しなくて良いのでunordered_mapで良いかなと感じています。使い方は下記のような感じ。

unordered_map<int, vector<int>> elementPositions;

第一引数のintはKeyのtypeを指定し、第2引数は値のtypeを指定している。今回は整数を指定すると、それに応じた配列が返ってくるような連想配列型elementPositionを定義したということになる。これだけだとCの構造体でも同じことができそうだけど、連想配列型はc++のautoを用いたループと相性が良い。例えばこんな感じ。

bool check_duplication(vector<int> declared_loc){
    unordered_map<int, vector<int>> elementPositions;
    bool duplicated = false;
    for (int i = 0; i < declared_loc.size(); ++i) {
        elementPositions[declared_loc[i]].push_back(i);
    }
    for (const auto& entry : elementPositions) {
        if (entry.second.size() > 1) {
            duplicated = true;
            return duplicated;
        }
    }
    return duplicated;
}

上記は引数のvector<int>の要素中に同じ整数が含まれていたら、重複を報告するような関数。要素ではなく要素の位置番号をvalue, そしてKeyはvector<int>の各値を持つことによってこれを実現している。注目したいのはconst auto& entry : elementPositionsの部分。const宣言によってループのbodyではentry変数が変化しないことを保証し、autoによって連想配列型であることを実行時に勝手に判定してもらう。そしてauto&であることからelementPositionsを参照して使用することが指定されている。こうすれば、構造体では難しかった全Keyや全Valueの探索が超!楽ちん。今回もconst auto&によって全valueをチェックして重複があるかだけを確認できる。これ、pythonでも実装は結構難しいかなと感じているので結構すごいなと思っています。

まとめ

C++はクセが強いけれど、できることも結構すごいのではないか?と感じ始めました。pybind11を使えばpythonの便利なところとc++の高速さを両立できるので革命が起きています。また、何かしらの備忘録を残そうと思っていますので、良ければ記事への励ましコメントなどをよろしくお願いします。モチベアップに繋がります。もちろん、記事で間違った内容などがありましたらご指摘よろ。
それではまた次回~~~。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です