ゆとりーなの日記

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

IXAudio2VoiceCallbackをどう扱うか

XAudio2でボイスのコールバックを扱うにはIXAudio2VoiceCallbackを継承して全メソッドをオーバーライドした俺俺コールバックを作るって事になってますが、このIXAudio2VoiceCallbackには仮想デストラクタが実はなかったりします。
というわけで、IXAudio2VoiceCallback*を使って派生クラスをdeleteした時の豊かな動作未定義を防ぐ為にprivate継承した方がいいだろうというのが私の持論だったりするのですが、そうするとボイスを生成するためのIXAudio2::CreateSourceVoiceに俺俺コールバックを渡すときに問題が起きます。
IXAudio2::CreateSourceVoiceはコールバックとしてIXAudio2VoiceCallback*を取るので、俺俺コールバックのポインタがIXAudio2VoiceCallback*に変換できなければいけませんが、private継承では当然暗黙の基底クラスポインタへの変換は効きませんし、static_castやdynamic_castも無力です。reinterpret_castだけはC++キャストの中で唯一この状況でキャストに成功しますが、型の扱いを変えるだけなので恐らく望んだ結果は得られないでしょう。が、実はCスタイルキャストがこの場合では望んだ結果をもたらしてくれるのです。以下はコンパイルが通る事を示したいがためだけに書いたコードなので実行とかはしてはいけません。

#include <XAudio2.h>

class my_callback : private IXAudio2VoiceCallback {
 private:
  void WINAPI OnStreamEnd() {}
  void WINAPI OnVoiceProcessingPassEnd() {}
  void WINAPI OnVoiceProcessingPassStart(UINT32) {}
  void WINAPI OnBufferEnd(void *) {}
  void WINAPI OnBufferStart(void *) {}
  void WINAPI OnLoopEnd(void *) {}
  void WINAPI OnVoiceError(void *, HRESULT) {}
};

int main() {
  IXAudio2 *xaudio;
  IXAudio2SourceVoice *voice;
  const WAVEFORMATEX format = {};
  my_callback callback;
  xaudio->CreateSourceVoice(&voice, &format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, (IXAudio2VoiceCallback*)&callback);
  return 0;
}

しかし多くのCプラーであればCスタイルキャスト使用撲滅条約機構に加盟しているはずなので、この様なCスタイルキャストの使用は不本意なはずです。というわけで代替案はないのかって話です。
一番楽なのが、IXAudio2::CreateSourceVoiceをラップした関数を作り、それを俺俺コールバックのfriendとして宣言しておくことが考えられます。

#include <XAudio2.h>

class my_callback : private IXAudio2VoiceCallback {
 private:
  void WINAPI OnStreamEnd() {}
  void WINAPI OnVoiceProcessingPassEnd() {}
  void WINAPI OnVoiceProcessingPassStart(UINT32) {}
  void WINAPI OnBufferEnd(void *) {}
  void WINAPI OnBufferStart(void *) {}
  void WINAPI OnLoopEnd(void *) {}
  void WINAPI OnVoiceError(void *, HRESULT) {}
  template <typename Callback>
  friend IXAudio2SourceVoice *create_source_voice(IXAudio2 *xaudio, Callback &callback);
};

template <typename Callback>
IXAudio2SourceVoice *create_source_voice(IXAudio2 * const xaudio, Callback &callback) {
  IXAudio2SourceVoice *voice;
  const WAVEFORMATEX format = {};
  xaudio->CreateSourceVoice(&voice, &format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &callback);
  return voice;
}

int main() {
  IXAudio2 *xaudio;
  my_callback callback;
  IXAudio2SourceVoice *voice = create_source_voice(xaudio, callback);
  return 0;
}

friendはカプセル化壊すから嫌いって人は俺俺コールバックがボイスも管理するとかですかね。

#include <XAudio2.h>

class my_callback : private IXAudio2VoiceCallback {
 public:
  explicit my_callback(IXAudio2 * const xaudio) : voice_() {
    const WAVEFORMATEX format = {};
    xaudio->CreateSourceVoice(&voice_, &format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this);
  }

 private:
  void WINAPI OnStreamEnd() {}
  void WINAPI OnVoiceProcessingPassEnd() {}
  void WINAPI OnVoiceProcessingPassStart(UINT32) {}
  void WINAPI OnBufferEnd(void *) {}
  void WINAPI OnBufferStart(void *) {}
  void WINAPI OnLoopEnd(void *) {}
  void WINAPI OnVoiceError(void *, HRESULT) {}
  IXAudio2SourceVoice *voice_;
};

int main() {
  IXAudio2 *xaudio;
  my_callback callback(xaudio);
  return 0;
}

これだと俺俺コールバック内で基底クラスのポインタへの変換が行われるのでアクセス的には問題ないですが、コールバックとボイス管理を一緒にやっちゃうとクラスが大きくなって設計的にやだって説もあるのでどれも一長一短ですね。
取り敢えず考えられる案

  • 基底クラスポインタで操作しないでねって書いておいてpublic継承
  • private継承してCスタイルキャストでIXAudio2::CreateSourceVoiceに渡す
  • private継承してIXAudio2::CreateSourceVoiceをラップした関数を俺俺コールバックでfriend宣言
  • private継承して俺俺コールバックがボイスも管理

他にいい案あったら例によってtwitterかコメントまでということで。