ポインターのポインターを返すコードで参照を使うべきではないと思う。特にDLLでは

DLLを使ったアプリケーション開発で、サードパーティーのコードではまった件。

特徴的なのがDLLでエクスポートされている関数の定義が下の様になっていた。
なお、DLL特有の修飾は今回関係ないので省略している。

void func( char*& data );

さて、この関数は、文字列をセットして返すことがわかっている。
では、値を返すバッファー(data)はどちらが用意するのだろうか?

通常、DLLを使うアプリケーションでは、バッファーを用意するのは利用者側だ。
そうでないと、メモリアロケーションがらみで予想しないトラブルが発生する場合があるためだ。

その場合、以下の様なコードが考えられる。簡便化のため、バッファサイズの最大値はあらかじめ予約されているとする(MAX_BUFFER)。

char* buf = new char[MAX_BUFFER];
func( buf );
...結果を利用するコード...
delete[] buf;

この場合、funcの実装が以下の様であれば正常に動作する。

void func( char*& data )
{
strcpy( data, "文字列" );
}

しかし、&は何のために必要なのかという問題がある。

普通、宣言は、以下の様にして&はつけない。

void func( char* data );

この関数の実装が実際どうなっていたかというと、以下の通り

void func( char*& data )
{
static char* buf = "文字列";
data = buf;
}
バッファーを用意せず、内部データへのポインターをコピーしている。
利用者側の先ほどのコードを実行すると、この実装の場合、下の箇所でアクセス違反が発生する。
delete[] buf;
違反が発生しない場合も、もっと複雑なオブジェクトの場合は、メモリーアクセスができなくて往生する。
利用者側からすると、原因がもっともわかりずらいパターンだ。今回はDLLのソースコードがあったので、気づくことができて助かった。

一般的に関数でポインターの参照渡しの場合は、そのポインターを変更しないのがマナーだと思う。
なぜなら、自分がはまった様に関数の利用者が勘違いしやすいためだ。
適当なオブジェクト(Object)のポインターを使った呼び出しで、参照渡しとポインター渡しの二つを比べると違和感を感じてもらえるだろうか。

 Object *pObj;

// ポインターを渡して、Objectを操作するのを期待していると利用者は考える
func_refer( pObj );


// ポインター自体を操作するのを期待していると利用者は考える
func_pointer( &pObj );

また、DLLの実装からすると、DLL側に保持データを持たせる(DLL側のメモリーアロケータだよりになる)状況はできるだけ避けるべきだ。
そうでなければ例外の発生などのシチュエーションでDLLとの接続が正しく終わらなかったとき、幽霊オブジェクトが発生するからだ。

私ならこうする。

// 関数の実装
void func( char* data, int nSize )
{
static char* buf = "文字列";
strncpy( data, buf, n );
}

// 呼び出し側
std::vector buf( MAX_BUFFER+1 );
buf[MAX_BUFFER] = 0;

func( &buf[0], MAX_BUFFER );