C++ - std::unique_ptr コンストラクターが外部ポインターを受け入れるのはなぜですか?

okwaves2024-01-24  5

私は cpp については比較的初心者で、スマート ポイントについて学んでいます。私は次のことを疑問に思っています:

左辺値を使用して std::unique_ptr を構築できるのはなぜですか?

悪いことを避けるために、右辺値を含む std::unique_ptr の構築のみを許可する方が安全ではないでしょうか?

std::unique_ptr<int> createInt() {
    int* a = new int(99);
    std::unique_ptr<int> foo(a);
    delete a;
    return foo;
}

そのようなものを書くのは気が狂っている必要があることはわかっていますが、コンパイラに怒鳴ってもらってもいいでしょう。そこで疑問に思うのは、なぜ unique_ptr の lvalue 初期化が必要なのかということです。

編集: ユーザー @aler egal が私の考えをよりエレガントに述べています:

"原則として、ptr の所有権を仮定して null に設定するコンストラクター unique_ptr<int>(int*&& ptr) を使用できます。これにより、この特定の例では use-after-free が発生しなくなります (std::move(a) が強制され、null ポインタで delete を呼び出しても効果がないため)、しかし、これは非常に奇妙なアンチパターンになります。」

5

ポインタ (および他のプリミティブ) の r 値は、実際には「移動」によってクリア/ヌルされません。したがって、ここでは役に立ちません。

– シャドウレンジャー

2020 年 9 月 3 日 3:13

6

C++ は乳母言語ではありません。それはあなたに足を撃つためのロープをたくさん与えます。

– エルジェイ

2020 年 9 月 3 日 3:13

2

原則として、ptr の所有権を仮定してそれを null に設定するコンストラクター unique_ptr<int>(int*&& ptr) を使用できます。これにより、この特定の例では use-after-free が回避されます (std::move(a) が強制されることと、null ポインターで delete を呼び出すためです)効果はありません) しかし、これは非常に奇妙なアンチパターンになります。より良いアプローチは、生のポインタを完全に所有しないことです。

– alter_igel

2020 年 9 月 3 日 3:20



------------------------

これは、ダングリング ポインタがどのようなものかを示す典型的な例です。 スマート ポインタは見た目とは対照的に、通常の (生の) ポインタを単にラッパーしているだけで、独自にメモリを管理し、いくつかの追加機能を提供します。

次の例を考えてみましょう。

int* someFunc() {
    int* ptr;
    int* ptr2 = ptr;
    delete ptr;
    return ptr2;
}

これが本質的にやっていることです。 それらはそのように作られていますこれらは、いつでも生の所有ポインタの代わりに使用できます。つまり、ぶら下げることもできます。したがって、左辺値の初期化が許可されていない場合、それはスマート ポインタを使用できないユースケースの 1 つです。私も同意しますが、これはどちらも使用してはいけないケースの 1 つです。

スマート ポインターを使用した上記のコードは、まさにあなたが試したものになります。

C++ はロジックを完全にユーザーに任せます。自分の足を撃ち抜きたいなら、どうぞ、C++ は吠えません。 C++ は、メモリ使用量、バッファ オーバーフロー、および削除されたメモリへのアクセスをチェックしません。簡単に言うと、これは C++ プログラマーの仕事であり、自分の足を撃ち抜きたい場合は、自由にそうすることができます。

また、通常は ctor よりも std::make_ptr() を使用することをお勧めします。前者は例外が有効であるためです。



------------------------

デザインは単にシンプルであるだけでなく、非常にシンプルであることを示唆するよく知られたデザイン原則があります。可能な限り単純な実装に複雑さを加えるには、正当な理由が必要です。この場合、提案される正当化は、次のような邪悪なことを避けることです。

std::unique_ptr<int> createInt() {
    int* a = new int(99);
    int* b = a;
    std::unique_ptr<int> foo(b);
    delete a;
    return foo;
}

おっと。何かを変更したようですが(新しい変数 b を導入しました)、同じ悪い結果が表示されます。ただ…この形では無理に移動構築してもダメです。構築後に b が null に設定された場合でも、a の値は変更されません。 (このフォームには何かが必要ですこれは、shared_ptr から unique_ptr を移動構築することに似ていますが、そのようなコンストラクターは存在しません – 正当な理由があります。)

そのため、コンストラクターの実装 (提供されたポインターを null にする必要がある) とコンストラクターの使用 (foo(a) は foo(std::move(a) になる必要がある) が複雑になります。 )))、それでも問題は実際には解決されていません。言語の最高の目標が安全であるなら、複雑さは依然として正当化されるかもしれません。ただし、C++ では安全性よりもパフォーマンス (使用した分だけ支払います) が優先されます。

C++ の多くの安全機能は、メモリの解放など、何か良いことを確実に行うためにあります。同じメモリを 2 回解放するなど、何か悪いことが行われるのを防ぐための対策はほとんど行われていません。いくつかの悪いことが警告をトリガーします

6

意味が分かりません。 「おっと。何かを変えたようですが、同じ悪い結果がそこにあります。」いいえ、そうではありません。まだ左辺値を使用して unique_ptr を作成しています。

– フランケロット

2020 年 9 月 4 日 21:06

@FRR 何か変更しませんでしたか?お使いのバージョンでは b という名前の変数を見た覚えがありません。あなたのバージョンの最後の 3 行だけを見ると、問題があることがわかります。私のバージョンの最後の 3 行だけを見ても、問題は明らかではありません。

– ジャミット

2020 年 9 月 4 日 21:34

「私のバージョンの最後の 3 行だけを見ても、問題は明らかではありません。」まさに、それが私の不満です。これが私がこの質問をしている理由です。分かりませんd コードの新機能。それは私と同じことを示しています。 shared_ptr の作成時に rvalue を使用してこの問題を引き起こすことができたなら、私は感銘を受けるでしょう。

– フランケロット

2020 年 9 月 4 日 22:51

@FRR 新機能: 変数 b.あなたのバージョンでは、 a をコンストラクターに渡してから、 a を削除します。したがって、コンストラクターがパラメーターとして使用される変数をクリアすると、削除には null ポインターが与えられます。私のバージョンでは、b をコンストラクターに渡してから a を削除します。したがって、コンストラクターがパラメーターとして使用される変数をクリアすると、deこの場合、元のポインタが与えられます。

– ジャミット

2020 年 9 月 5 日 0:54

2

@FRR いいえ、あなたは間違っています。右辺値の使用を強制しても、パラメータ内に新しい値をネストすることは強制されません。 std::unique_ptr<int> foo(std::move(b));あなたのシナリオに完全に適合します。

– ジャミット

2020 年 9 月 5 日 3:30

総合生活情報サイト - OKWAVES
総合生活情報サイト - OKWAVES
生活総合情報サイトokwaves(オールアバウト)。その道のプロ(専門家)が、日常生活をより豊かに快適にするノウハウから業界の最新動向、読み物コラムまで、多彩なコンテンツを発信。