126

コピー オン ライトはstd::string、C++11 で準拠を実装する実行可能な方法ではないことを理解していましたが、最近議論になったとき、そのステートメントを直接サポートできないことに気付きました。

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

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

または、この制限はstd::string、COW ベースのstd::string. std::stringこの場合、「C++11 は COW ベースの実装を効果的に禁止する」という章と節のスタイルの派生に興味があります。

4

7 に答える 7

123

標準の 21.4.1 p6 に従って、反復子/参照の無効化は次の場合にのみ許可されるため、許可されません。

— const 以外の basic_string への参照を引数として取る標準ライブラリ関数への引数として。

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

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

于 2012-08-30T15:06:12.427 に答える
53

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

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

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]ss2p

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

C++11 標準では、 への呼び出しの後に続くかどうかに関係なく、 への呼び出しがポインター、参照、または反復子を無効にする可能性があるため、その動作は許可されなくなりました。operator[]()data()

したがって、上記の例は C++11 で機能する必要がありますが、libstdc++ の種類の COW 文字列では機能しないため、その種類の COW 文字列は C++11 では許可されていません。

于 2015-03-22T20:55:21.843 に答える
20

つまり、CoW はより高速な文字列を作成するための受け入れ可能なメカニズムです...しかし...

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

他の理由は、[]オペレーターが文字列データを返し、他の誰かが変更されていないと予想する文字列を上書きするための保護がないことです。c_str()とについても同様data()です。

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

提案は言う:

提案

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

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

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

に続く

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

ロープは STLPort と SGI STL の一部です。

于 2012-08-30T15:01:54.997 に答える
5

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

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

[...]

2効果: 表 64 に示すクラスのオブジェクトを構築しますbasic_string。 [...]

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

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

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

于 2012-08-30T15:02:40.470 に答える
4

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

それにかんする

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()を参照する参照、ポインター、およびイテレーターを無効にしたり、つまり、 COW データのコピー。このサポートは C++11 で削除されました。


COW は C++11 の無効化規則によっても禁止されていますか?

執筆時点で解決策として選択されており、非常に支持されているため明らかに信じられている別の回答では、次のように主張されています。

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

その主張は正しくなく、主に次の 2 つの点で誤解を招きます。

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

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

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

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

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

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

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

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

「最初の呼び出し」COW サポートを含む C++03 §21.3/5 :

シーケンスの要素を参照する参照、ポインター、および反復子は、そのオブジェクトbasic_stringを次のように使用すると無効になる場合があります。 — 非メンバー関数(21.3.7.8)、 (21. ​​3.7.9)、および(21.3. 7.9)。 — への引数として。 — 呼び出し関数とメンバー関数。— 、、、、、およびを除く 非メンバー関数の呼び出し。—イテレータを返すおよび の形式を除く上記の使用のいずれかに続いて、非メンバー関数, , ,への最初の呼び出しbasic_string
swap()operator>>()getline()
basic_string::swap()
data()c_str()
constoperator[]()at()begin()rbegin()end()rend()
insert()erase()constoperator[]()at()begin()rbegin()end()、またはrend()

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


O(1) 要件が無視された場合はどうなりますか?

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

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

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

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

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

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


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

于 2016-07-29T05:53:27.857 に答える
2

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

于 2012-08-30T15:00:07.417 に答える