ゆとりーなの日記

日記的な事を書いて行くと思はれる

COM風の繼承にC++の常識は通󠄁用しない

C++を打つ者󠄁にとつては「"インターフェースクラス"の"デストラクタ"を"virtual"にする」事は日本語で云ふ「"私は"の"は"は"わ"ではなく"は"と書く」くらゐの常識であるが、DirectX等でお馴染みのCOMではこの常識は通󠄁用しない。
例へばXAudio2なんかではボイスのコールバックを實裝するのにIXAudio2VoiceCallback(IUnknownから派󠄂生してゐないから嚴密にはCOMではないけれども雰󠄁圍氣COMつぽいからCOM風)を繼承するのだが、どこにもデストラクタの定義は存在しない。假裝デストラクタな事は迚も期待できない訣だがもしかするとマイクロソフト樣のパワーならなにかしらのワンチャンがあるのではないかと一瞬期待したものゝ

#include <iostream>
#include <xaudio2.h>

struct hage {
  virtual ~hage() { std::cout << "~hage" << std::endl; }
};

struct hoge : IXAudio2VoiceCallback, hage {
  virtual ~hoge() {std::cout << "~hoge" << std::endl;}
  STDMETHODIMP_(void) OnStreamEnd() override {}
  STDMETHODIMP_(void) OnVoiceProcessingPassEnd() override {}
  STDMETHODIMP_(void) OnVoiceProcessingPassStart(UINT32) override {}
  STDMETHODIMP_(void) OnBufferStart(void *) override {}
  STDMETHODIMP_(void) OnBufferEnd(void *) override {}
  STDMETHODIMP_(void) OnLoopEnd(void *) override {}
  STDMETHODIMP_(void) OnVoiceError(void *, HRESULT h) override {}
};

int main() {
  {
    hage *h = new hoge;
    delete h;
  }
  {
    IXAudio2VoiceCallback * h = new hoge;
    delete h;
  }
}

實行結果

~hoge
~hage

無情󠄁にもワンチャンなどなかつた。
C++的には不思議な感じがするがCOM(風)インターフェイスを基底クラスに持つクラスは基底クラスのポインタ經由ではデストラクタが呼ばれないので、デストラクタで後始末をする處理を書くと地雷である。
續いて繼承して色々實裝する事で文󠄁字の描畫が豪華にできる事に定評󠄁がある先程󠄁よりCOMCOMしいIDWriteTextRendererの場合。こつちはIXAudio2VoiceCallbackと違󠄂つてガチCOM(とは言へCLSIDとかは附けないのでこちらもやつぱりCOM風?)のインターフェースを實裝しないといけない。Direct3Dとかでお馴染みReleaseとかが出てくるので見るからに基底クラスのポインタ經由での解放處理を要󠄁求されさうである。結論から云ふとRelease內で參照カウンタの値が0ならdeleteを呼ぶのが正解らしい。

struct hoge_text_renderer : IDWriteTextRenderer {
  hoge_text_renderer() : count_{1} {}
  virtual ~hoge_text_renderer() { std::cout << "~hoge_text_renderer" << std::endl; }
  STDMETHODIMP IsPixelSnappingDisabled(__maybenull void *, __out BOOL *) override {return S_OK;}
  STDMETHODIMP GetCurrentTransform(__maybenull void *, __out DWRITE_MATRIX *) override {return S_OK;}
  STDMETHODIMP GetPixelsPerDip(__maybenull void *, __out FLOAT *) override {return S_OK;};
  STDMETHODIMP DrawGlyphRun(__maybenull void *, FLOAT, FLOAT, DWRITE_MEASURING_MODE, __in const DWRITE_GLYPH_RUN *, __in const DWRITE_GLYPH_RUN_DESCRIPTION *, IUnknown *) override {return S_OK;}
  STDMETHODIMP DrawUnderline(__maybenull void *, FLOAT, FLOAT, __in const DWRITE_UNDERLINE *, IUnknown *) override {return S_OK;}
  STDMETHODIMP DrawStrikethrough(__maybenull void *, FLOAT, FLOAT, __in const DWRITE_STRIKETHROUGH *, IUnknown *) override {return S_OK;}
  STDMETHODIMP DrawInlineObject(__maybenull void *, FLOAT, FLOAT, IDWriteInlineObject *, BOOL, BOOL, IUnknown *) override {return S_OK;}
  STDMETHODIMP_(ULONG) AddRef() override {return InterlockedIncrement(&count_);}

  STDMETHODIMP_(ULONG) Release() override {
    const auto count = InterlockedDecrement(&count_);
    if (!count) {
      delete this;
    }
    return count;
  }
  
  STDMETHODIMP QueryInterface(const IID &riid, void **object) override {
    if (__uuidof(IDWriteTextRenderer) == riid || __uuidof(IDWritePixelSnapping) == riid || __uuidof(IUnknown) == riid) {
      *object = this;
      this->AddRef();
      return S_OK;
    }
    *object = nullptr;
    return E_FAIL;
  }

 private:
  ULONG count_;
};

int main() {
  IDWriteTextRenderer *good = new hoge_text_renderer;
  IDWriteTextRenderer *bad = new hoge_text_renderer;
  good->Release();
  delete bad;
}

實行結果

~hoge_text_renderer

これならRelease經由で解放する分󠄁にはデストラクタ呼び出しも安心である。要󠄁するに自分󠄁で繼承して作つたCOMCOMしいクラスはデストラクタで後始末をする處理を書くと地雷と云ふよりかは通󠄁常のCOMポインタと同樣にdeleteするなつて事になりさうである。普通󠄁のCOMなら生成󠄁がまづnewぢやないので然程󠄁違󠄂和感ないのだが、自作COM風オブジェクトはnewして作つてReleaseで解放しないといけないと云ふのは慣れないと不思議な感じがする。