C ++ 11でのCOW std :: string実装の合法性


117

コピーオンライトはstd::stringC ++ 11での適合を実装するための実行可能な方法ではないというのが私の理解でしたが、最近議論されたとき、私は自分がそのステートメントを直接サポートできないことに気付きました。

C ++ 11がCOWベースの実装を許可しないことは正しいstd::stringですか?

もしそうなら、この制限は新しい標準のどこかに(どこで)明示的に述べられていますか?

または、この制限は、std::stringCOWベースのの実装を妨げる新しい要件の複合効果であるという意味で、暗黙のうちに含まれていstd::stringますか。この場合、「C ++ 11はCOWベースのstd::string実装を効果的に禁止する」という章と詩のスタイルの派生に興味があります。


5
COW文字列のGCCバグはgcc.gnu.org/bugzilla/show_bug.cgi?id=21334#c45です。libstdc ++のstd :: stringの新しいC ++ 11準拠の実装を追跡するバグの1つは、gcc.gnu.org
bugzilla /

回答:


120

標準21.4.1 p6に従い、イテレータ/参照の無効化は、

—引数として非const basic_stringへの参照を使用する標準ライブラリ関数の引数として。

— operator []、at、front、back、begin、rbegin、end、およびrendを除く、非constメンバー関数の呼び出し。

COW文字列の場合、non-const operator[]を呼び出すにはコピーを作成する必要があり(参照を無効にする)、これは上記の段落では許可されていません。したがって、C ++ 11でCOW文字列を使用することはもはや合法ではありません。


4
理論的根拠:N2534
MM

8
-1ロジックは水を保持しません。COWコピーの時点では、無効化できる参照または反復子はありません。コピーを実行する全体のポイントは、そのような参照または反復子が現在取得されているため、コピーが必要です。ただし、C ++ 11ではCOWの実装が許可されていない可能性があります。
乾杯とhth。-Alf、

11
@ Cheersandhth.-Alf:COWが許可されている場合、ロジックは次のようになります。c1 std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1]; はaへの参照です。次に、「コピー」します。次に、2回目に参照を取得しようとすると、同じバッファーを指す2つの文字列があるため、コピーを作成して非const参照を取得する必要があります。これは、最初に取得された参照を無効にする必要があり、上記のセクションに違反しています。
Dave S 14

9
@ Cheersandhth.-アルフは、によると、この、少なくともGCCのCOWの実装がない DAVESが言っている正確に何をすべきか。したがって、少なくともそのスタイルのCOWは規格で禁止されています。
Tavian Barnes

4
@Alf:この回答は、非const operator[](1)はコピーを作成する必要があり、(2)コピーすることは違法であると主張しています。これらの2つの点のどちらに同意しませんか?最初のコメントを見ると、実装は、少なくともこの要件の下では、アクセスされるまで文字列を共有できるようですが、読み取りと書き込みの両方のアクセスを共有解除する必要があります。それはあなたの推論ですか?
Ben Voigt 2015年

48

Dave Sgbjbaanbの答えは正しいです。(そしてLuc Dantonも正しいですが、COW文字列を禁止するのは、元のルールではなく、それを禁止することによる副作用です。)

しかし、混乱を解消するために、さらに説明を追加します。さまざまなコメントが、次の例を示すGCCバグジラに関する私のコメントにリンクしいます。

std::string s("str");
const char* p = s.data();
{
    std::string s2(s);
    (void) s[0];
}
std::cout << *p << '\n';  // p is dangling

その例の要点は、GCCの参照カウント(COW)文字列がC ++ 11では無効である理由を示すことです。C ++ 11標準では、このコードが正しく機能する必要があります。pC ++ 11では、コードの何も無効にできません。

GCCの古い参照カウントstd::string実装を使用すると、そのコードp 無効化され、ぶら下がりポインタになるため、未定義の動作になります。(何が起こるかというとときということでs2構築され、それはでデータを共有するsが、非const参照を取得することを経由してs[0]共有を解除するためにデータを必要とするので、s参照があるため、「コピーオンライト」を行いs[0]、潜在的に書き込みに使用することができs、その後、s2行きますスコープ外で、p)が指す配列を破棄します。

C ++ 03標準は、21.3 [lib.basic.string] p5でその動作明示的に許可しdata()おり、最初の呼び出しの呼び出しの後に、operator[]()ポインター、参照、および反復子が無効になる可能性があると述べています。したがって、GCCのCOW文字列は有効なC ++ 03実装でした。

C ++ 11標準で、この動作を許可していません。これoperator[]()は、への呼び出しの後に続くかどうかに関係なく、への呼び出しによってポインター、参照、または反復子が無効になることはないためdata()です。

上記の例だから、必見の C ++ 11での作業が、ないと仕事にlibstdc ++の種類COW文字列の、COW文字列の種類は、11 C ++で許可されていないため、という。


3
への呼び出し.data()(およびポインター、参照、または反復子のすべての戻り)で共有を解除する実装は、その問題の影響を受けません。つまり、(不変の)バッファはいつでも共有解除されるか、外部参照なしで共有されます。この例についてのコメントは、コメントとしての非公式のバグレポートとして意図していると思いましたが、誤解してしまい申し訳ありません。しかし、ここで説明するような実装を検討することでわかるように、noexcept要件が無視された場合にC ++ 11で正常に機能しますが、この例では形式について何も述べていません。必要に応じて、コードを提供できます。
乾杯とhth。-Alf

7
文字列へのほとんどすべてのアクセスで共有を解除すると、共有のメリットがすべて失われます。COWの実装は、標準ライブラリがそれをとして使用するのを煩わしくするために実用的であるstd::string必要があり、C ++ 11の無効化要件を満たす便利でパフォーマンスの高いCOW文字列を実証できることを心から疑っています。したがってnoexcept、最後に追加された仕様はCOW文字列の禁止の結果であり、根本的な理由ではないと私は主張します。N2668は完全に明確に見えますが、なぜそこに概説されている委員会の意図の明確な証拠を否定し続けるのですか?
Jonathan Wakely 2015年

また、これdata()はconstメンバー関数であるため、他のconstメンバーと同時に呼び出しても安全である必要があります。たとえばdata()、文字列のコピーを作成する別のスレッドと同時に呼び出しても安全です。したがって、constのものも含めて、すべての文字列操作にmutexのすべてのオーバーヘッド、またはロックフリーの可変参照カウント構造の複雑さが必要になります。結局、変更またはアクセスしない場合にのみ共有が得られます。あなたの文字列、非常に多くの文字列は、参照カウントが1になります。コードを提供してくださいnoexcept。保証は無視してかまいません。
Jonathan Wakely 2015年

2
いくつかのコードを組み合わせるだけでbasic_string、129のメンバー関数と無料の関数があることがわかりました。抽象化のコスト:この最適化されていない、最適化されていない新しい0番目のバージョンコードは、g ++とMSVCの両方で50〜100%遅くなります。これはスレッドセーフではありません(十分に活用して簡単shared_ptrだと思います)。タイミングの目的で辞書の並べ替えをサポートするだけで十分ですが、モジュロバグbasic_stringは、C ++のnoexcept要件を除いて、参照カウントが許可されるという点を証明します。 github.com/alfps/In-principle-demo-of-ref-counted-basic_string
乾杯とhth。-アルフ


20

それは、CoWはより高速な文字列を作成するための許容可能なメカニズムです...しかし...

マルチスレッドコードが遅くなります(多くの文字列を使用している場合、1つだけの書き込みかどうかを確認するためのロックにより、パフォーマンスが低下します)。これが、CoWが数年前に殺された主な理由でした。

他の理由は、[]オペレーターが文字列データを返すことです。他の誰かが変更しないと期待している文字列を上書きする保護はありません。同じことが、にも適用されるc_str()data()

クイックグーグルによると、マルチスレッドは基本的に(明示的にではなく)事実上許可されなかった理由です。

提案は言う:

提案

すべてのイテレータと要素のアクセス操作を安全に同時に実行できるようにすることを提案します。

シーケンシャルコードでも操作の安定性を高めています。

この変更により、コピーオンライトの実装が事実上禁止されます。

に続く

コピーオンライト実装からの切り替えによるパフォーマンスの最大の潜在的な損失は、非常に大きな読み取りが主な文字列を持つアプリケーションのメモリ消費の増加です。ただし、これらのアプリケーションではロープの方が優れた技術的ソリューションであると考えており、ライブラリTR2に含めるためにロープの提案を検討することをお勧めします。

ロープはSTLPortおよびSGIs STLの一部です。


2
operator []の問題は実際には問題ではありません。constバリアントは保護を提供し、non-constバリアントは常にその時点でCoWを実行するオプションを持っています(または、本当にクレイジーで、ページフォールトを設定してトリガーします)。
Christopher Smith

+1問題に移動します。
乾杯とhth。-アルフ

5
std :: cow_stringクラスが含まれていなかったり、lock_buffer()などが含まれていなかったりするのは、ばかげています。多くの場合、実際には。
Erik Aronesty、2015

私は代替のigロープの提案が好きです。他に利用可能な代替タイプと実装があるかどうか。
Voltaire、

5

21.4.2からbasic_stringコンストラクタと代入演算子[string.cons]

basic_string(const basic_string<charT,traits,Allocator>& str);

[...]

2 効果basic_string表64に示すように、クラスのオブジェクトを作成します。[...]

表64は、この(コピー)コンストラクターを介してオブジェクトを構築した後this->data()、次の値を持っていることを示しています。

str.data()が最初の要素を指す配列の割り当てられたコピーの最初の要素を指します

他の同様のコンストラクターにも同様の要件があります。


+1 C ++ 11が(少なくとも部分的に)COWを禁止する方法を説明します。
乾杯とhth。-Alf、

すみません、疲れました。バッファが現在共有されている場合、.data()の呼び出しでCOWコピーをトリガーする必要があること以外は説明されていません。それでも有用な情報なので、賛成票を立てました。
乾杯とhth。-Alf、

1

文字列が連続して格納されていることが保証され、文字列の内部ストレージへのポインタを取得できるようになりました(つまり、&str [0]は配列の場合と同じように機能します)、有用なCOWを作成することはできません。実装。あなたはあまりにも多くのもののためにコピーを作成する必要があります。だけでも、使用operator[]またはbegin()非const文字列にコピーが必要になります。


1
C ++ 11の文字列は連続して格納されることが保証されていると思います。
mfontanini 2012

4
過去には、あなたはすべてのそれらの状況でコピーをしなければならなかったし、それが問題...ではなかった
デビッド・ロドリゲス- dribeas

@mfontaniniはい、でも以前はそうではありませんでした
Dirk Holsopple 2012

3
C ++ 11は文字列が連続していることを保証しますが、COW文字列の禁止とは直交しています。GCCのCOW文字列は連続しているため、「有用なCOW実装を作成することは不可能である」というあなたの主張は明らかに偽物です。
Jonathan Wakely、2015年

1
@supercatは、(たとえばを呼び出すことによってc_str())バッキングストアを要求することはO(1)である必要があり、スローできず、データレースを導入してはならないため、遅延連結する場合、これらの要件を満たすのは非常に困難です。実際には、唯一の合理的なオプションは、常に連続したデータを格納することです。
Jonathan Wakely、2015年

1

basic_stringC ++ 11以降ではCOWは禁止されていますか?

について

アム私はC ++ 11がのCOWベースの実装を認めていないことを修正しますかstd::string

はい。

について

もしそうなら、この制限は、明示的に(どこ)新規格のどこかに記載されていますか?

ほとんど直接的に、COW実装で文字列データのO( n)物理コピーを必要とする多くの操作の一定の複雑さの要件による。

たとえば、メンバー関数の場合

auto operator[](size_type pos) const -> const_reference;
auto operator[](size_type pos) -> reference;

…COW実装では、どちらも文字列データのコピーをトリガーして文字列値の共有を解除しますが、C ++ 11標準では

C ++ 11§21.4.5/ 4

複雑さ:一定の時間。

…このようなデータのコピー、つまりCOWを除外します。

C ++ 03によるCOW実装をサポートしていないの呼び出しできるように、これらの一定の複雑さの要件を有し、一定の制約条件の下で、でoperator[]()at()begin()rbegin()end()、またはrend()文字列の項目を参照無効の参照、ポインタとイテレータに、つまりはおそらく被るAにCOWデータのコピー。このサポートはC ++ 11で削除されました。


COWもC ++ 11無効化ルールによって禁止されていますか?

執筆時にソリューションとして選択された別の回答では、非常に賛成されているため、明らかに信じられており、

COW列について、呼び出し非はconst operator[][C ++ 11§21.4.1/ 6]上記[引用]段落によって禁止され、コピーを作成(及び参照を無効化)が必要となります。したがって、C ++ 11でCOW文字列を使用することはもはや合法ではありません。

その主張は不正確であり、主に2つの点で誤解を招きます。

  • これは、非constアイテムアクセサーのみがCOWデータのコピーをトリガーする必要があることを誤って示しています。
    ただし、constアイテムアクセサーはデータコピーをトリガーする必要があります。これにより、クライアントコードが参照またはポインターを形成できるようになるため(C ++ 11では)、COWデータコピーをトリガーできる操作を介して後で無効にすることはできません。
  • COWデータのコピーによって参照が無効になる可能性があると誤って想定しています。
    ただし、正しい実装では、COWデータのコピー、文字列値の共有解除は、無効化できる参照がある前の時点で行われます。

の正しいC ++ 11 COW実装がどのように機能するかを確認するbasic_stringために、これを無効にするO(1)要件が無視される場合、文字列が所有権ポリシーを切り替えることができる実装を考えてください。文字列インスタンスは、ポリシーSharableで始まります。このポリシーがアクティブな場合、外部アイテム参照はありません。インスタンスは一意のポリシーに移行でき、.c_str()(少なくとも内部バッファーへのポインターを生成する場合)への呼び出しなどでアイテム参照が作成される可能性がある場合は、移行する必要があります。値の所有権を共有する複数のインスタンスの一般的なケースでは、これは文字列データのコピーを伴います。一意のポリシーへの移行後、インスタンスは、割り当てなどのすべての参照を無効にする操作によってのみ、共有可能に戻ることができます。

したがって、COW文字列が除外されているという回答の結論は正しいものの、提示された推論は正しくなく、誤解を招く可能性があります。

この誤解の原因は、C ++ 11の附属書Cの非規範的な注記であると思います。

C ++ 11§C.2.11[diff.cpp03.strings]、§21.3について:

変更basic_string要件は参照カウント文字列を許可しなくなりました
根拠:参照カウント文字列との無効化は微妙に異なります。この変更により、この国際規格の動作(sic)が正規化されます。
元の機能への影響:この国際標準では、有効なC ++ 2003コードの実行が異なる場合があります

ここで、理論的根拠は、C ++ 03の特別なCOWサポートを削除することにした主な理由を説明しています。この理由、その理由は、標準がCOWの実装を効果的に禁止する方法ではありません。標準では、O(1)要件によるCOWは許可されていません。

要するに、C ++ 11無効化ルールはのCOW実装を除外しませんstd::basic_string。しかし、それらは、g ++の標準ライブラリ実装の少なくとも1つにあるような、合理的に効率的な無制限のC ++ 03スタイルのCOW実装を除外します。特別なC ++ 03 COWサポートによりconst、無効化のための微妙で複雑なルールを犠牲にして、特にアイテムアクセサーを使用して実用的な効率を実現しました。

C ++ 03§21.3/ 5には「ファーストコール」COWサポートが含まれています。

参照、ポインタ、の要素を参照する反復子basic_string配列は、以下の使用によって無効にすることができるbasic_stringオブジェクト:
-非メンバ関数への引数としてswap()(21.3.7.8)、operator>>()(21.3.7.9)、及びgetline()(21.3。 7.9)。
—の引数としてbasic_string::swap()
—呼び出しdata()およびc_str()メンバー関数。
-非呼び出しconstを除いて、メンバ関数をoperator[]()at()begin()rbegin()end()、とrend()
-の形以外は、上記の用途のいずれかに続いinsert()及びerase()非最初の呼び出し、戻りイテレータconstメンバ関数operator[]()at()begin()rbegin()end()、またはrend()

これらのルールは非常に複雑で微妙なので、多くのプログラマがいたとしても、正確な要約を提供できるとは思えません。私ができなかった。


O(1)要件が無視されるとどうなりますか?

たとえばC ++ 11の一定の時間要件operator[]が無視される場合、COW for basic_stringは技術的には実現可能ですが、実装するのは困難です。

COWデータのコピーを行わずに文字列の内容にアクセスできる操作には、次のものがあります。

  • による連結+
  • を介して出力し<<ます。
  • basic_string標準ライブラリ関数への引数としての使用。

後者は、標準ライブラリが実装固有の知識と構造に依存することが許可されているためです。

さらに、実装では、COWデータのコピーをトリガーせずに文字列コンテンツにアクセスするためのさまざまな非標準関数を提供できます。

主な複雑な要因は、C ++ 11では、basic_stringアイテムへのアクセスがデータのコピー(文字列データの非共有化)をトリガーする必要があるが、スローないようにする必要があることです。たとえば、C ++ 11§21.4.5/ 3「スローなし」。そのため、通常の動的割り当てを使用してCOWデータをコピーするための新しいバッファーを作成することはできません。これを回避する1つの方法は、実際に割り当てることなくメモリを予約できる特別なヒープを使用して、文字列値への各論理参照に必要な量を予約することです。そのようなヒープでの予約と予約解除は一定の時間O(1)であり、すでに予約されている量を割り当てることは、noexcept。標準の要件に準拠するために、このアプローチでは、個別のアロケーターごとにそのような特別な予約ベースのヒープが1つ必要になると思われます。


注:
¹ constアイテムアクセサーは、クライアントコードがデータへの参照またはポインターを取得できるため、COWデータコピーをトリガーします。これは、非constアイテムアクセサーなどによってトリガーされた後のデータコピーによって無効にすることが許可されていません。


3
あなたの例は、C ++ 11に対して正しくない実装の良い例です。おそらく、C ++ 03にとっては正しいものでした。」はい、それが例のポイントです。古いイテレータ無効化規則に違反しないためC ++ 03では合法だったCOW文字列が表示され、新しいイテレータ無効化規則に違反しているためC ++ 11では合法ではありません。また、上記のコメントで引用した声明とも矛盾します。
Jonathan Wakely

2
最初に共有可能ではないと共有可能だと言っていたら、私は議論しなかったでしょう。何かが最初に共有されたと言うことは単に混乱するだけです。共有していますか?それは言葉が意味するものではありません。しかし、私は繰り返します。C++ 11イテレータ無効化ルールは、実際には使用されなかった(そして許容できないパフォーマンスをもたらす)いくつかの仮想的なCOW文字列を禁止しないというあなたの主張は、COW文字列の種類を確実に禁止します。これは実際に使用されましたが、やや学術的で無意味です。
Jonathan Wakely

5
提案されたCOW文字列は興味深いですが、それがどれほど有用かはわかりません。COW文字列のポイントは、2つの文字列が書き込まれた場合にのみ文字列データをコピーすることです。提案された実装では、ユーザー定義の読み取り操作が発生したときにコピーが必要です。コンパイラその読み取りを知っている場合でも、コピーする必要があります。さらに、一意の文字列をコピーすると、その文字列データが(おそらく共有可能な状態に)コピーされるため、COWは無意味になります。したがって、複雑さの保証がなければ、次のように書くことができます... 本当にひどい COW文字列。
Nicol Bolas

2
したがって、複雑さの保証がどのような形式のCOWの記述妨げていることは技術的に正しい一方で、本当に有用な形式のCOW文字列の記述を妨げているのは、実際には[basic.string] / 5です。
Nicol Bolas

4
@JonathanWakely:(1)あなたの引用は問題ではありません。ここに質問があります。「C ++ 11がstd :: stringのCOWベースの実装を許可しないことは正しいですか?もしそうなら、この制限は新しい標準のどこかに(どこで)明示的に述べられていますか?」(2)COW std::stringがO(1)の要件を無視すると非効率的であるというあなたの意見はあなたの意見です。パフォーマンスがどのようなものになるかはわかりませんが、その主張は、この回答との関連性よりも、その感触、それが伝えるヴァイブスに対してより提唱されていると思います。
乾杯とhth。-2016

0

私は常に不変の牛について疑問に思っていました。牛が作成されると、別の牛からの割り当てによってのみ変更できるため、標準に準拠することになります。

簡単な比較テストのために今日試してみる時間がありました:文字列/牛によってキーが付けられたサイズNのマップで、すべてのノードがマップ内のすべての文字列のセットを保持しています(オブジェクトの数はNxNです)。

文字列のサイズが300バイト以下で、N = 2000の牛の方がわずかに高速で、使用するメモリがほぼ1桁少なくなります。以下を参照してください。サイズはキロバイト単位、ランbは牛の場合です。

~/icow$ ./tst 2000
preparation a
run
done a: time-delta=6 mem-delta=1563276
preparation b
run
done a: time-delta=3 mem-delta=186384
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.