natvisファイルで多次元の動的配列を表示する。

 Visual Studioデバッグしている時、例えばSTLなどの中身を綺麗に整頓された状態で監視することが出来る。これを自作クラスに対しても行うことは出来るのだろうか?私は標準ライブラリでは設計上の問題が生じる場合によく自作の機能を設計して使うのだが、それらをデバッグ時に表示できないと困る。
 結論から言うと、可能だ。natvisファイルを作り、その中にデバッグ中の表示方法を定義すれば良い。基本的な使い方は「natvis」とググれば簡単に見つかるので省略する。

 例えば、自分でnew、delete(あるいはmalloc、free)するような動的配列を、Visual Studioデバッグ機能はうまく表示してくれない。普通の1次元配列はちょっと探せば解説が見つかるし難しくないのだが、多次元配列の場合は少し面倒である。ので、ここにちょっと纏めておく。

 シンプルな2次元動的配列クラスを考える。

#include <iostream>
#include <array>

struct Matrix
{
    Matrix(size_t xsize, size_t ysize) : mSize{ xsize, ysize }, mPtr(new double[xsize*ysize]) {}
    Matrix(size_t xsize, size_t ysize, double init)
        : mSize{ xsize, ysize }, mPtr(new double[xsize*ysize])
    {
        size_t size = mSize[0] * mSize[1];
        for (size_t i = 0; i < size; ++i) mPtr[i] = init;
    }
    ~Matrix() { delete[] mPtr; }
    double& operator()(size_t x, size_t y) { return mPtr[x * mSize[1] + y]; }
    const double& operator()(size_t x, size_t y) const { return mPtr[x * mSize[1] + y]; }
private:
    double* mPtr;
    size_t mSize[2];
};

int main()
{
    Matrix x(2, 3, 0);
    x(1, 2) = 3;//このあたりでデバッグを止めて中身を見てみよう。
}

 さてこのとき、mPtrの中身をきちんと2次元配列として表示したい。次のようなnatvisファイルをプロジェクトに追加しよう。

<?xml version="1.0" encoding="utf-8"?> 
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">

  <Type Name="Matrix">
    <DisplayString>{{ Size = {mSize[0]}, {mSize[1]} }}</DisplayString>
    <Expand>
      <ArrayItems>
        <Direction>Forward</Direction>
        <Rank>2</Rank>
        <Size>$i==0?mSize[0]:mSize[1]</Size>
        <ValuePointer>mPtr</ValuePointer>
      </ArrayItems>
    </Expand>
  </Type>
  
</AutoVisualizer>

 1次元配列と比べて定義しなければならない項目が少し増えている。
 Directionは配列の方向がどのようであるかを示している。Forwardとした場合、メモリ空間の位置と要素のindexとが[0, 0]、...、[0, nx]、[1, 0]、...、[1, nx]、...のように対応していると解釈される。Backwardなら[0, 0]、...、[nx, 0]、[0, 1]、...、[nx, 1]、...のような配置だと推定される。
 Rankは配列の次元数だ。今回は2次元配列なので、2を与えている。
 Sizeには、x方向とy方向のサイズを定義する。このとき$iという変数が自動的に与えられるのだが、これが0のときにx方向のサイズを、1のときにy方向のサイズを与える。これは3次元以降の場合も同様で、z以降も$i==2、$i==3、...のときに各値を返すよう定義すればよい。
 ValuePointerは単にポインタを与えるだけだ。1次元配列と同様である。

 ちょっとした応用例としては、OpenCVのMatなども上の定義で何とかなったと記憶している。あれも所詮は2次元(細かな色の数の指定などを考えれば3次元)の配列である。