Sean Parent は、Going Native 2013 でInheritance Is The Base Class of Evilというタイトルの講演を行いました。std::shared_pointer<const T>
20 分 50 秒の時点で、不変 (const) 型 ( ) への共有ポインターには値のセマンティクスがあると述べています。これは正確にはどういう意味ですか?変更可能な (非定数) 型 ( ) への共有ポインターと異なるのはなぜstd::shared_pointer<T>
ですか?
4 に答える
残念ながら、 Going Native 2013でのすべての講演と同様に、非常にタイトなタイム スケジュールに制約されていました。私たちにとって幸いなことに、Sean Parent は昨年の C++Now で、Value Semantics and Concepts-based Polymorphismという、より詳細な講演を行いました。それは同じ資料をカバーしており、おそらくあなたの質問に答えるでしょう. とにかく説明してみます…。
序章
型が持つことができるセマンティクスには、次の 2 種類があります。
- 値のセマンティクス。
- 参照セマンティクス。(ポインターセマンティクスと呼ばれることもあります。)
この 2 つがどのように異なるのか、またどちらが優先されるのかについては、何ページにもわたって続けることができます。単純に、値型を使用するコードはより簡単に推論できるとしましょう。
つまり、値型のインスタンスのどの時点でも、予測できないことは何も起こりません。参照される値は、参照を保持するコードの他の部分間で共有されるため、参照型では保証できないことです。
言い換えれば、参照型は遠く離れたコードによって変更される可能性があるため、予測しにくいということです。たとえば、呼び出した関数によって、参照されている値が変更される可能性があります。さらに悪いことに、スレッド化が関係している場合、参照された値をたまたま操作する別のスレッドによっていつでも参照型が変更される可能性があります。このため、Sean Parent は、 aを使用するコードについて推論できるようになると、 ashared_ptr
はグローバル変数と同じくらい優れていると述べています。
そうは言っても、目の前の質問に答える準備をしておく必要があります。
質疑応答
値型の場合、ポインタ型なのに値型のように振る舞うのT
はなぜですか?shared_ptr<const T>
const T
指しているを変更することはできないため、ポインター/参照型が予測しにくいことについて述べたことはすべて当てはまりません。T
const 値型なので、不意に変更される心配がなくなりました。
に変更を加えたい場合はT
、そのコピーを作成する必要があり、 を保持している他のユーザーは変更のshared_ptr<const T>
影響を受けません。さらに、 Copy-on-writeと呼ばれるメカニズムを使用して、コピーを値型内に隠すこともできます。これは、Sean Parent が最終的に行ったことのようです。
Sean Parent のように (リンクされた C++Now プレゼンテーションでも) 質問に答えたと思いますが、補足を加えてもう少し先に進みましょう.....
大きな補遺:
(これを取り上げ、コメントで例を提供してくれた@BretKuhnsに感謝します。)
この概念全体には、厄介な問題が 1 つあります。shared_ptr<const T>
値型のように振る舞うということは、そのインスタンスへのすべての生きているポインタ/参照T
がconst
. これは、const
修飾子が一方通行であるためです。 a を保持すると、 のインスタンスを変更できなくなるshared_ptr<const T>
可能性がありますが、他の誰かが非へのポインタ/参照を介してを変更するT
ことは妨げられません。T
const
これをshared_ptr<const T>
知っていると、値型へのすべての生きているポインターがconst
. しかし、そのようなことを知るには、すべての使用法に関するコードのグローバルな知識が必要shared_ptr<const T>
です。これは、値型の問題ではないものです。そのため、次のように言う方が理にかなっているかもしれません: Ashared_ptr<const T>
は値セマンティクスをサポートするために使用できます。
ちなみに、私はGoing Native 2013に参加していたので、左前に私の後頭部が見えるかもしれません。
私は3つの例を挙げています。a
3 つのケースすべてで、 content を含む変数を作成します"original value"
。b
次に、次のように言って別の変数を作成auto b = a;
し、このステートメントの後にa
contentを割り当てます"new value"
。
a
とb
が値セマンティクスを持っている場合、b
のコンテンツは"original content"
. 実際、まさにこれが と で起こりstring
ますshared_ptr<const string>
。の概念的な意味auto b = a;
は、これらのタイプと同じです。というほどでshared_ptr<string>
はb
ないが、内容はあるだろう"new value"
。
コード (オンラインデモ):
#include <iostream>
#include <memory>
#include <string>
using namespace std;
void string_example() {
auto a = string("original value");
auto b = a; // true copy by copying the value
a = string("new value");
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}
void shared_ptr_example() {
auto a = make_shared<string>("original value");
auto b = a; // not a copy, just and alias
*a = string("new value"); // and this gonna hurt b
cout << "a = " << *a << endl;
cout << "b = " << *b << endl;
cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}
void shared_ptr_to_const_example() {
auto a = make_shared<const string>("original value");
auto b = a;
//*a = string("new value"); // <-- now won't compile
a = make_shared<const string>("new value");
cout << "a = " << *a << endl;
cout << "b = " << *b << endl;
cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}
int main() {
cout << "--------------" << endl;
cout << "string example" << endl;
string_example();
cout << "------------------" << endl;
cout << "shared_ptr example" << endl;
shared_ptr_example();
cout << "---------------------------" << endl;
cout << "shared_ptr to const example" << endl;
shared_ptr_to_const_example();
}
出力:
--------------
string example
a = new value
b = original value
&a == &b ? false
------------------
shared_ptr example
a = new value
b = new value
&a == &b ? false
---------------------------
shared_ptr to const example
a = new value
b = original value
&a == &b ? false
そうは言っても、彼にもう少し時間があればよかったのですが、そのプレゼンテーションの後、まだ疑問に思っていることがいくつかあります。時間が足りなかっただけだと確信しています。彼は優れたプレゼンターのようです。
彼が言いたいのは、値のセマンティクスをエミュレートするために使用できるということです。
値セマンティクスの主な特徴は、同じ内容を持つ 2 つのオブジェクトが同じであるということです。整数は値の型です。5 は他の 5 と同じです。これを、オブジェクトがID を持つリファレンス メカニズムと比較してください。a
[1, 2] を含む リストは、[1, 2]b
を含むリストと同じでa
はありませんb
。のIDはのIDとa
は異なります。b
これは直観的になりがちです... 言葉にすると奇妙に聞こえるだけです。値型と参照型の直感的な感覚を理解せずに、C++ で 3 日間を過ごす人はいません。
可変値型があり、それをコピーしたい場合は、実際にオブジェクトの内容をコピーする必要があります。これは高価です。
Sean が言及しているトリックは、オブジェクトが不変の場合、オブジェクト全体をコピーする必要はなく、古いオブジェクトを参照するだけでよいということです。これははるかに高速です。
shared_ptr<const T>
彼は、a の存在は、オブジェクトへのすべてのハンドルもshared_ptr<const T>
(つまり、読み取り専用)であることを暗示していると想定しているようです。
もちろん、これは生のconst T*
構成要素の存在がオブジェクトがconst
.
おそらく、「不変性」の意味を誤解しているでしょう。const T
質問で、それらは同じだと言いましたが、そうではありません。