行列初期化の罠
プロローグ
よーしパパ、要素が 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=()
うーん。