DOSEIの日記

技術メモ+日常ログ

行列初期化の罠

プロローグ

よーしパパ、要素が 0 で初期化された行列つくっちゃうぞー

  cv::Mat A(150, 100, CV_32S, 0);
  A.at<int>(20,10) = 30;

コンパイルも通ったし、バッチリだ!

エピローグ

セグフォー!

後日談

cv::Matコンストラクタはたくさんあり、そのなかには値で初期化をするものがある。

cv::Mat::Mat(int rows, int cols, int type, const Scalar& s);

これが使われると思ってしまったのが敗因である。cv::Scalar (の基の cv::Scalar_) は、 int からの implicit なコンストラクタをもっているので、

  cv::Mat A(150, 100, CV_32S, 20);

なら、問題なく 20 で初期化されると言う点がややこしい。では、なぜ 0 のときは何が起こっているのか。実は、 0 の時は、

cv::Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP);

が呼ばれている。つまり、 0 はヌルポインタと解釈されたわけである。この場合、 Mat は自動管理される領域の替わりに、ユーザが指定した領域を保持する。その結果、何も領域を持っていない Mat が出来上がり、要素にアクセスするとセグメンテーションフォルトとなる。
ちなみに、
cv::Mat::operator=(cv::Scalar) も、全要素への値の代入を行うのだが、

  A = 0;

は別の理由で拒否される。これは、 0 から暗黙に変換できる Mat を引数にとるオーバーロードもあるので、曖昧だと言われてしまう。このポインタを一つとるコンストラクタ

cv::Mat::Mat(const CvMat* m, bool copyData=false);

は、レガシーな型からの変換である。
もし、ゼロで初期化を指定したい場合は、

  cv::Mat A(150, 100, CV_32S, cv::Scalar(0)); // by constructor
  A = cv::Scalar(0);                          // by operator=()

うーん。