投稿者「kitzz」のアーカイブ

COM と WRL::ComPtr

これまで中途半端にOpenGLESとMetalを使ったことがあるくらいでDirect3Dはまったくいじったことのないオッサンが「まあMetalとそんな変わらんだろう」といきなりDirect3D12に挑戦して四苦八苦してます。ロートルオジサンにはつらい。。。

で表題の件ですがDirectXのオブジェクトは基本COM。Direct3D12になってもCOM。
D3D12からは言語はC++しかサポートせず、そしてオブジェクトの保持や破棄は全てプログラマが責任持って管理するようになったのにCOMを使う利点って何だろう。。。
もうValkanのようにイチから設計し直して欲しかったがそれならValkanをどうぞということだろうか。

それでCOMですが、アラフィフになってえっCOMって何? なロートルおっさんなので勉強しました。

  • 参照カウント方式。AddRefしたらReleaseする。カウンタがゼロになったら解放される
  • 戻り値や引数でCOMを返却する関数は関数側でAddRefして返す。関数から受け取った側は必要なくなったらReleaseする。

MRC時代のObjective-Cの参照カウンタと似たかんじで、これはOK。

次に WRL::ComPtr。 参照カウンタの面倒を自動化してくれる。すばらしい。

ただ、参照カウンタをどう扱うかのポリシーみたいなやつのドキュメントが見当たらない。どう使っていいのかいまいちピンとこない。しかたないのでComPtrのヘッダをみてみる。

WRL::ComPtrはコンストラクタや代入でCOMオブジェクトを受け取る時に必ずAddRefし、管理しなくなる時に必ずReleaseする。
これは徹底している。参照カウンタが元に戻ることが保証される。
右辺値参照にも対応し、moveする場合は参照カウンタは増えない。

これはなかなか良い。

だけども、Create系関数から返却されたオブジェクトをComPtrで扱おうとするとあれっとなる。
Create系関数から返却されたオブジェクトはすでにAddRefされた状態で返ってくるので、こいつはAddRefせずにReleaseだけ行う。
だけどコンストラクタや代入でWRL::ComPtrに渡すとAddRefしてしまう!!

で、どうするんだろうとサンプルコードなどをみると、ComPtrでCOMオブジェクトを受けるのはこんなカンジになってる

ファッ!?? ってなりますよね。 なりますよね?

&deviceって何??
なんでComPtrのアドレスを直にD3D12CreateDeviceの引数にぶっこんでるの??

と思ってComPtrのヘッダをみると、単項 & 演算子がオーバーロードされていた。こいつがDetails::ComPtrRef<ComPtr<T>>を返す。さらに Details::ComPtrRefの operator InterfaceType**() が呼ばれて、元のComPtrのReleaseAndGetAddressOf()を呼ぶ。。。。 つまり、上記のCreateDeviceのところは

と書くのと同じである。

これでようやく、ああ、ComPtr内部で持ってるオブジェクトのポインタのアドレスを渡して直接オブジェクトを得るんだな。この時AddRefはされないんだな。そしてComPtrのデストラクタで無事Releaseされるんだな。というのが理解できた。

これくらいのことをヘッダ辿って調べるのは一般的なC++erだったらきっと至極当然のことなのであろう。だからWRL::ComPtrのこの辺の挙動を疑問に思うエントリがぐぐっても出てこないんだ。たぶん。
でもロートルおじさんはこの辺の挙動を理解するのに苦労したのでこうして書き留めるよ。

しかしこれ、 &device って書くよりも device.ReleaseAndGetAddressOf() って書く方が圧倒的にわかりやすいよねえ。。。

ICC17.0の変数テンプレートが少〜し変よ〜

ICC17.0が出たのでさっそくアップデートしました。

  • Support C++14 variable templates (N3651)

となってるのでワクテカで変数テンプレートを試してみたわけですがちょっとおかしい。

とりあえずconstexprな変数テンプレートのコンパイルは通る。

だがしかし、constexpr な変数テンプレートを特殊化するとコンパイルエラーになる。

これ、ClangやMSVCではコンパイル通るけど、ICC17.0では「a constexpr variable declaration must be a definition」というエラーが出る。

変数テンプレートのconstexprを取り除いて

ってするとICC17.0でもコンパイル通る。。。。 これってICCの変数テンプレートの実装ミスだよなあ。。。。。

std::make_shared にまつわるエトセトラ

C++11が世に出てもう5年経つので世の中のC++erさんにはもう常識なハナシかとは思いますがロートルおじさんは最近初めて知りました。結構ビックリ。

std::shared_ptr( new T(args…) );
std::make_shared( args… );

make_sharedはてっきり記述を楽するだけのものかと思ってたら、オブジェクトと管理用のメモリ領域を一体で確保してくれるとのこと。
一方上のようにフツーにnewしたものからshared_ptrを作成すると、オブジェクトのメモリと管理領域のメモリがそれぞれ別々に確保される。

C++プログラマーよ!std::make_sharedを安易に使うべからず! : 株式会社CFlatの明後日スタイルのブログ
make_shared で確保されたメモリ領域は,それを参照する weak_ptr が無くならない限り解放されない : 野良C++erの雑記帳

・管理領域を一体で確保するからメモリの割り当て回数が少なくて済む。その分速い
・make_sharedが管理領域をまとめて独自にメモリを確保するのでクラスのoperator new/deleteが呼ばれない
・make_sharedの中からオブジェクトを構築するのでpublicでないコンストラクタを呼び出す手段がない
・オブジェクトを指すshared_ptrが全てなくなりdesutructされた後にまだそのオブジェクトを指すweak_ptrが残っている場合、管理領域を残す必要があるためにそれと一緒に確保された(destructされた後の)オブジェクトのメモリ領域が残ってしまう。そのオブジェクトを指すweak_ptrも全部無くなれば解放される。

うーん、make_sharedを使うか使わないか、結構意識しないといけないですねコレ。。。