[解決済み] C++ - std::shared_ptr または boost::shared_ptr への参照の渡し方
質問
もし、ある関数が
shared_ptr
を扱う必要がある場合、その関数にその参照を渡した方が効率的ではないでしょうか?
shared_ptr
オブジェクトのコピーを避けるため) の方が効率的ではないでしょうか?
また、どのような副作用が考えられるでしょうか?
私は2つの可能なケースを想定しています。
1) 関数の内部で、以下のように引数のコピーが作成される。
ClassA::take_copy_of_sp(boost::shared_ptr<foo> &sp)
{
...
m_sp_member=sp; //This will copy the object, incrementing refcount
...
}
2) 関数の内部では、引数は次のようにしか使われません。
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
を渡す正当な理由が見当たりません。
boost::shared_ptr<foo>
を参照ではなく値で渡す正当な理由が見当たりません。値で渡すと、コピーによって参照カウントが一時的に増加し、関数スコープを終了するときに減少するだけです。
私は何かを見落としているのでしょうか?
いくつかの回答を読んだ後、明確にするために。私は、時期尚早の最適化に関する懸念に完全に同意し、常に最初にプロファイルを作成してから、ホットスポットに取り組むようにしています。私の質問は、純粋に技術的なコードの観点からのもので、もし私が何を言いたいかわかるなら。
どのように解決するのですか?
明確な
shared_ptr
のインスタンスがある限り、この
shared_ptr
がスコープ内にある限り、その参照カウントが少なくとも1であるため、それが指すオブジェクトはまだ存在し続けることを(可能な限り)保証するものです。
Class::only_work_with_sp(boost::shared_ptr<foo> sp)
{
// sp points to an object that cannot be destroyed during this function
}
つまり
shared_ptr
への参照を使用すると、その保証が無効になります。つまり、2つ目のケースでは
Class::only_work_with_sp(boost::shared_ptr<foo> &sp) //Again, no copy here
{
...
sp->do_something();
...
}
どうして
sp->do_something()
がヌルポインタのために吹き飛ばされないとどうしてわかるのでしょうか?
それはすべて、コードの「...」セクションに何があるかによるのです。最初の「...」セクションで何かを呼び出すと、(コードの別の部分のどこかで)その副作用として
shared_ptr
をクリアする副作用があったらどうでしょう?そして、それがたまたま唯一残った明確な
shared_ptr
になってしまったら?ちょうどあなたがそれを使おうとしているところで、オブジェクトとはおさらばです。
というわけで、この質問には2通りの答えがあります。
-
関数本体の途中でオブジェクトが死なないと確信できるまで、プログラム全体のソースを非常に注意深く調べます。
-
パラメータを参照ではなく、別個のオブジェクトに戻すように変更します。
ここで適用される一般的なアドバイスのビット: プロファイラーで現実的な状況で製品をタイムアウトし、行いたい変更がパフォーマンスに大きな違いをもたらすことを決定的に測定するまで、パフォーマンスのためにコードにリスクのある変更をわざわざ行う必要はありません。
コメントした JQ のための更新
以下は、作為的な例です。意図的に単純にしているので、間違いは明らかでしょう。実際の例では、間違いは実際の詳細の層に隠されているため、それほど明白ではありません。
メッセージをどこかに送信する関数があります。それは大きなメッセージかもしれません。
std::string
を使うよりも、複数の場所に転送される際にコピーされる可能性のある
shared_ptr
を文字列に変換します。
void send_message(std::shared_ptr<std::string> msg)
{
std::cout << (*msg.get()) << std::endl;
}
(この例ではコンソールに"send"するだけです).
今度は、前のメッセージを記憶するための機能を追加したいと思います。次のような動作が必要です: 最近送信されたメッセージを含む変数が存在しなければなりませんが、メッセージが現在送信されている間は、前のメッセージは存在してはなりません (変数は送信前にリセットされるべきです)。そこで、新しい変数を宣言します。
std::shared_ptr<std::string> previous_message;
次に、指定したルールに従って、関数を修正します。
void send_message(std::shared_ptr<std::string> msg)
{
previous_message = 0;
std::cout << *msg << std::endl;
previous_message = msg;
}
つまり、送信を開始する前に現在の前のメッセージを破棄し、送信完了後に新しい前のメッセージを保存することができるのです。すべて順調です。ここにいくつかのテストコードがあります。
send_message(std::shared_ptr<std::string>(new std::string("Hi")));
send_message(previous_message);
そして予想通り、これは
Hi!
を2回表示します。
さて、今度はメンテナさんがやってきて、コードを見て考えます。おい、あのパラメータ
send_message
は
shared_ptr
:
void send_message(std::shared_ptr<std::string> msg)
に変更できるのは明らかです。
void send_message(const std::shared_ptr<std::string> &msg)
これがもたらすパフォーマンスの向上について考えてみましょう! (私たちはあるチャンネルで典型的な大きなメッセージを送信しようとしているので、パフォーマンスの向上は測定不可能なほど小さくなることは気にしないでください)。
しかし、本当の問題は、今、テスト コードが未定義の動作 (Visual C++ 2010 デバッグ ビルドでは、クラッシュする) を示すということです。
Maintainer 氏はこのことに驚きましたが、防御的なチェックを
send_message
に防御的なチェックを加え、この問題の発生を阻止しようとしました。
void send_message(const std::shared_ptr<std::string> &msg)
{
if (msg == 0)
return;
しかし、もちろんそれでもクラッシュしてしまいます。
msg
は決して null ではないからです。
send_message
が呼び出されたとき、決して null にはなりません。
私が言うように、些細な例ではすべてのコードが非常に近くにあるため、間違いを見つけるのは簡単です。しかし、実際のプログラムでは、互いのポインターを保持するミュータブル オブジェクト間の関係がより複雑であるため、簡単に を作る となり、その間違いを検出するために必要なテストケースを作成することが難しくなります。
簡単な解決策としては、関数が
shared_ptr
がずっと非NULLであり続けることに依存したい場合、簡単な解決策は、関数が自分自身の真の
shared_ptr
への参照に依存するのではなく、既存の
shared_ptr
.
欠点は、コピーされた
shared_ptr
は自由ではありません。quot;lock-free" の実装でさえ、スレッド保証を守るためにインターロックされたオペレーションを使わなければなりません。そのため、プログラム中に
shared_ptr
を
shared_ptr &
. しかし、これはすべてのプログラムに対して安全に行える変更ではありません。プログラムの論理的な意味を変えてしまうのです。
同じようなバグは
std::string
の代わりに
std::shared_ptr<std::string>
の代わりに、そして
previous_message = 0;
をクリアにするために、私たちは言いました。
previous_message.clear();
そうすると、症状は未定義の動作ではなく、空のメッセージを誤って送信してしまうことになります。非常に大きな文字列の余分なコピーの代償は、例えば
shared_ptr
をコピーするコストよりもはるかに大きいかもしれないので、トレードオフは異なるかもしれません。
関連
-
[解決済み】C++でランダムな2倍数を生成する
-
[解決済み】IntelliSense:オブジェクトに、メンバー関数と互換性のない型修飾子がある
-
[解決済み】Visual C++で "Debug Assertion failed "の原因となる行を見つける。
-
[解決済み] 解決済み] `pthread_create' への未定義の参照 [重複] [重複
-
[解決済み】Visual Studioのデバッガーエラー。プログラムを開始できません 指定されたファイルが見つかりません
-
[解決済み] スタックアロケーションにより初期化されていない値が作成された
-
[解決済み] UbuntuにBoostをインストールする方法
-
[解決済み] using namespace std;」はなぜバッドプラクティスだと言われるのですか?
-
[解決済み] const std::string & をパラメータとして渡す時代は終わったのでしょうか?
-
[解決済み】shared_ptrは参照で渡すべきか、値で渡すべきか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】coutはstdのメンバではない
-
[解決済み】C++でint型に無限大を設定する
-
[解決済み】Visual Studio 2015で「非標準の構文; '&'を使用してメンバーへのポインターを作成します」エラー
-
[解決済み】非静的メンバ関数への参照を呼び出す必要がある
-
[解決済み】C++でユーザー入力を待つ【重複あり
-
[解決済み】エラー:strcpyがこのスコープで宣言されていない
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み】標準ライブラリにstd::endlに相当するタブはあるか?
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件
-
[解決済み] リファレンスとポインタの使い分け