中身がポインタのvectorから特定要素を削除

vectorから特定の条件でフィルターをかけて要素を削除するにはerase-removeというイディオムがある。std::remove_ifで条件に当てはまる要素を後ろに移動させ、返り値のiteratorから元のvectorの最後尾までをeraseにかける、というもの。

cf. std::remove_ifは実際には要素の削除を行わない。

これを要素がポインタのvectorに対してやってみたところうまくいかなかった。挙動を見ると、条件に当てはまらない(=残したい)要素でも削除されてしまうものが存在するようだった。

ググってみるとこんなのがstackoverflowに見つかった

c++ - Use std::remove_if on a std::vector filled with pointers - Stack Overflow

remove_ifではなくpartitionを使えといっている。 ということでpartitionを調べてみると、条件にあう要素を前方に、合わない要素を後方に移動させ、2つ目のグループの最初のiteratorを返す、というものらしい。

remove_ifと何が違うのか?と思ったがこういうのも上がっていた

Difference between partition() and remove() functions in C++ - Stack Overflow

どうやらremove_ifは条件に当てはまる不要な要素を後方に集めるという処理はしないものだった。なので結論として、remove_ifの仕様を勘違いしていることが原因だった。

remove_ifのreferenceを見ると

cpprefjp.github.io

条件に当てはまらない要素(有効な要素)を前方に集めると書いてあるが、条件に当てはまる(不要な要素)を後ろに移動させるとは書いていない。

よってポインタを要素とするvectorから特定の要素を削除したい場合はstd::partitionを使う必要がある。例えばこんな感じ

struct A {
    int id;
    A(id): id(id) {}
}

auto v = {new A(1), new A(2), new A(3)}

auto it = std::partition(v.begin(), v.end(), [](A *a) {
    return a.id == 2;
});

std::for_each(v.begin(), it, [](A *a) { delete a; });
v.erase(v.begin(), it);

photonとの通信で接続が切れたことを検知する

photonとの接続が切断されたことを検知する方法を調べたのでメモ

公式のドキュメントはこちら
https://doc.photonengine.com/ja-jp/realtime/current/reference/analyzing-disconnects

photonのサーバとクライアントはheartbeatのメッセージを投げあって、接続されているか常に確認しあっている模様

Photon接続の両側(クライアントとサーバ)は送信したリライアブルコマンドが相手側に届いたことをを監視します。即時に (現在の往復時間に基づいて)ACKがない場合、リライアブルコマンドを再送します。

よって、このメッセージの応答がサーバから返らないことを検知できれば切断を検知したことになる。

photonのc++sdkを見ると、Listenerとして以下のようなinterfaceが定義されている。 http://doc-api.photonengine.com/en/cpp/current/

これらを呼び出すの部分をc++ sdk(v4.1.9.0)内で探すとLoadBalancing::Client::onStatusChanged()
にある。その中でstatusに応じて対応するlistenerが呼ばれている。statusは以下のファイルで定義されており、それぞれコメントが書いてあったのでgistに貼った。 ExitGames::Photon::StatusCode https://gist.github.com/y-kamiya/eb3d2dfdf05952bfa4d8cef1ef643940

onStateChangeのロジックと対応させて見ると、切断に関するlistenerは以下のような感じだった。

  • clientErrorReturn
    • クライアントからパケットを送出できなかった場合
    • 対応するstatusCode
      • SEND_ERROR
  • connectionErrorReturn
    • タイムアウトやサーバ側からの切断要求、なんらかの例外発生
    • 対応するstatusCode
      • EXCEPTION
      • EXCEPTION_ON_CONNECT
      • INTERNAL_RECEIVE_EXCEPTION
      • TIMEOUT_DISCONNECT
      • DISCONNECT_BY_SERVER
      • DISCONNECT_BY_SERVER_USER_LIMIT
      • DISCONNECT_BY_SERVER_LOGIC

実際にlistenerが呼ばれる場合を試してみたところ少なくとも以下は確認できた

  • 自分の端末を機内モードwifi OFFなどした場合
    • 自端末でclientErrorReturnが呼ばれる
  • 通信相手の端末を機内モードwifi OFFなどした場合
    • 自端末でconnectionErrorReturnが呼ばれる

ちなみに、connectionErrorReturnの場合はその中でdisconnet()も呼んでいるため、その後disconnetReturnも呼ばれる

c+11でメンバ変数初期化のされ方

c+11で書いていてクラス内のメンバの中に、コンストラクタで初期化子が定義されてないものがあったのでどのような挙動になるのか調べてみた。

これらを見るとわかりやすい
C++11: Syntax and Feature
C++の初期化は分かりにくい - ぷろみん

まず、staticが付いたものなどglobalスコープの変数はゼロ初期化されてことが保証されている (不定にはならない)

それ以外だと、デフォルト初期化と値初期化の場合で挙動が異なる

デフォルト初期化の場合

以下のSのように初期化子のカッコを付けずにオブジェクトを生成した場合のこと クラスのメンバ変数に対し、コンストラクタでのメンバー初期化子が指定されてない場合もこれになる

int main() {
    S s;
    ...
}
  • メンバがクラス型である場合
    • デフォルトコンストラクタが呼ばれる
  • メンバが配列型である場合
    • 各要素がそれぞれデフォルト初期化
  • それ以外の場合

値初期化の場合

以下のSのように初期化子のカッコを付けて定義したもの 他にもnewをつけた場合やコンストラクタでのメンバー初期化子などもこれ

int main() {
    S s();
    ...
}
  • メンバがクラス型である場合
    • デフォルトコンストラクタが呼ばれる
  • メンバが配列型である場合
    • 各要素がそれぞれデフォルト初期化
  • それ以外の場合、
    • ユーザ定義コンストラクタを持つ場合
    • ユーザ定義コンストラクタを持たない場合
      • ゼロ初期化