株式会社エス・スリー・フォー

18.2 コピーオンライト

クラス RWCString, RWWString, and RWTValVirtualArray<T> の各クラスは、コピー作業を最小限にとどめるために「コピーオンライト」という技法を使用します。参照カウンタへポインタを実装しているため、この技法は速くて分かりやすい値の意味論を持つという利点があります。

次に、この技法がどのように働くかについて説明します。次のように、コピーコンストラクタを使って RWCString を別の RWCString で初期化します。

RWCString(const RWCString&);

この時、どちらかの文字列に書き込みが行われるまで、2つの文字列は同じデータを共有します。書き込みが行われる時点で、データのコピーが作成され、2つの文字列は別々になります。書き込み時にだけコピー作業を行い、読み取り専用のメモリをあまり使わない文字列のコピーを作成します。次の例では、4つのオブジェクトのうち 1 つが文字列の変更を試みるまで、これらのオブジェクトがどのように 1つの文字列のコピーを共有するかを見ることができます。

#include <rw/cstring.h>

RWCString g;                                     // グローバルオブジェクト

void setGlobal(RWCString x) { g = x; }

main(){
  RWCString a("kernel");                                     // 1
  RWCString b(a);                                            // 2
  RWCString c(a);                                            // 3

  setGlobal(a);       // まだ "kernel" のコピーは 1 つ!       // 4

  b += "s";           // ここで b は独自のデータを持つ: "kernels"  // 5
}
  • //1 文字列 kernel を格納するためのメモリの実際の割り当てと初期化は、RWCString オブジェクト a で行われます。
  • //2-//3 オブジェクト b と c が a から作成されるとき、これらは、オリジナルデータでの参照カウントをインクリメントし、戻ります。この時点で、同じデータを見ているオブジェクトが 3 つあります。
  • //4 setGlobal() 関数が g の値、つまりグローバル RWCStringを同じ値に設定します。ここで、参照カウントが 4 になりますが、まだ、kernel のコピーはただ 1 つです。
  • //5 最後に、オブジェクト b が文字列の値を変更しようとします。プログラムは参照カウントが 1 より大きい値であること、つまり文字列が複数のオブジェクトによって共有されていることを知ります。この時点で、文字列のクローンが作成され、変更されます。新しくコピーされた文字列の参照カウントが 1 になる一方、オリジナルの文字列の参照カウントが 3 に戻ります。

18.2.1 さらに分かりやすい例

RWCString のコピーはメモリを使わないため、ポインタを格納するのではなく、オブジェクト内部に値として格納することをお勧めします。次に比較を示すように、これにより管理が非常に簡単になります。背景色と文字色を設定できるウィンドウがあると仮定します。単純に考えた場合は、次のようにポインタで色を設定するでしょう。

class SimpleMinded {
  const RWCString*  foreground;
  const RWCString*  background;
public:
  setForeground(const RWCString* c) {foreground=c;}
  setBackground(const RWCString* c) {background=c;}
};

表面上は、文字列のコピーを 1つだけ作成すればいいので、この手段を使いたくなってしまうでしょう。この意味で、setForeground() を呼び出すのは効率的に見えます。ところが、よく見てみると、結果の意味論が混乱しています。foreground によって指されている文字列が変更した場合はどうなるのでしょうか? 文字色を変更するのでしょうか? このような場合、Simple クラスはどのようにして変更を知るのでしょうか? また、管理上にも問題があります。色付き文字を削除する前に、その文字列を指しているものがあるかどうかを知る必要があります。

次に、もっと簡単な方法を示します。

class Smart {
  RWCString  foreground;
  RWCString  background;
public:
  setForeground(const RWCString& c) {foreground=c;}
  setBackground(const RWCString& c) {background=c;}

ここでは、foreground=c という代入で値の意味論が使われています。Smart クラスが使う色は、完璧に曖昧性がありません。文字列が変更されないかぎりデータのコピーが作成されないため、「コピーオンライト」によりプロセスも効率的に行われます。次の例では、白が変更されるまで、white のただ 1 つのコピーが維持されます。

Smart window;
RWCString color("white");

window.setForeground(color);   // 白への 2 つの参照

color = "Blue";   // 1 つの参照が白で、もう 1 つが青