変数の内容の複製: コピーコンストラクタ

遅かれ早かれ、zval コンテナの内容を別のコンテナに 代入したい場面が訪れるでしょう。これは口で言うほど簡単ではありません。 というのも zval コンテナには、 型情報だけではなく Zend の内部データへの参照も含まれているからです。 例えば、配列やオブジェクトの場合は、その大きさに応じて 多くのハッシュテーブルエントリが入れ子構造になっています。 ある zval を別のコンテナに代入すると、 ハッシュテーブルのエントリを複製するのではなく (単に) その参照が複製されるだけになります。

このような複雑な形式のデータをコピーするには、 コピーコンストラクタ を使用します。 コピーコンストラクタは、一般的には 演算子のオーバーロードをサポートしている言語で定義されており、 複雑な型をコピーするために使用されています。 そのような言語でオブジェクトを定義すると、"=" 演算子をオーバーロードできるようになります。この演算子の一般的な役割は、 rvalue (演算子の右側を評価した結果) の内容を lvalue (同じく左側の結果) に代入することです。

オーバーロード とはこの演算子に別の意味を割り当てることです。 通常は、演算子に対して関数コールを割り当てるために用いられます。 プログラム内でそのようなオブジェクトに対してこの演算子が使用されると、 lvalue と rvalue をパラメータとして関数がコールされます。 この関数内で、パラメータの情報を使用して "=" 演算子にさせたい動作を行います (通常は、コピーを拡張した動作になるでしょう)。

PHP の zval コンテナについても、この 「コピーを拡張した動作」が必要になります。 配列の場合は、この拡張コピー動作によって 配列に関連するすべてのハッシュテーブルの内容を新しく作成するようにします。 また文字列の場合は適切なメモリの確保が必要になります。

Zend では、これを行うための関数として zend_copy_ctor() を提供しています (以前の PHP では、同じ機能を持つ pval_copy_constructor() という関数がありました)。

一番わかりやすい例として、 「複雑な型を引数として受け取ってそれを変更し、変更した内容を返す」 という関数を考えてみましょう。

zval *parameter;
   
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &parameter) == FAILURE)
   return;
}
   
// ここでパラメータに何らかの変更を加えます

// 変更した後のコンテナを返します
*return_value = *parameter;
zval_copy_ctor(return_value);

この関数の最初の部分は、単に引数を取得しているだけのごく普通の処理です。 しかし、(省略している部分で) 変更を加えた後が面白いところです。 parameter のコンテナが、 (事前に定義済みの) return_value コンテナに代入されます。 ここで、コンテナの中身をうまく複製するためにコピーコンストラクタがコールされます。 コピーコンストラクタは、与えられた引数に対して直接動作します。 標準的な返り値は、 失敗した場合に FAILURE、 成功した場合に SUCCESS となります。

この例でコピーコンストラクタのコールを省略した場合、 parameterreturn_value の両方が同じ内部データを指すことになり、return_value は同じデータ構造への無効な参照ということになってしまいます。 parameter が指すデータに変更を加えると、それが return_value にも影響を及ぼしてしまいます。 つまり、別の複製を作るためにはコピーコンストラクタの使用が必須であるということです。

Zend API において、コピーコンストラクタに対応するデストラクタは zval_dtor() です。これはコンストラクタと反対の動作をします。