4

ポインターをパラメーターとして受け入れる関数があるとします。この関数はstd::vector<>::push_back()、このポインターのライフサイクルを管理するために使用するため、例外をスローできます。次のように宣言すると:

void manage(T *ptr);

次のように呼び出します。

manage(new T());

ポインタを にプッシュする例外がスローされた場合std::vector<>、事実上メモリリークが発生していますよね?

次のように関数を宣言します。

void manage(std::auto_ptr<T> ptr);

私の問題を解決しますか?

std::auto_ptr最初にスタックに割り当てて(例外をスローすることはできなかったと思います)、ポインターに対する所有権を取得できるようにすることを期待しています。安全。

次に、関数内で生のポインターを にプッシュしますがstd::vector<>、これも安全です。これが失敗した場合、ポインターは追加されませんが、スマート ポインターは引き続きポインターを所有しているため、破棄されます。プッシュが成功した場合、そのポインターに対するスマート ポインターの所有権を削除して戻ります。これは例外をスローできないため、常に問題ありません。

私の理論は正しいですか?

- 編集 -

いいえ、できないと思います。これを行うには、rvalue への非 const 参照を取得する必要があります (スマート ポインターから所有権を奪うため)。私は書かなければならないだろう

std::auto_ptr<T> ptr(new T());
manage(ptr);

それが機能するためには、私の場合はかなり不便です。これを書いているのは、コードをあまり汚さずに RAII を実装できるようにするためです。そんなことをしても何の役にも立ちません。キャッチ22になります。

-- 編集 2 --

読者がすぐに参照できるように、ジェイソン・オレンドルフがここに言ったことをここに引っ張り出すと、最終的な解決策は次のようになります。

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

これにより、右辺値への無用な非 const 参照の問題が解決されます。

コーディング中のこのクラスを終了したら、誰かが役に立つと思った場合に備えて、ここに投稿します。

-- 編集 3 --

わかりました、ここで多くの議論がありましたが、以前に明確にしておくべき重要なポイントがあります。通常、stackoverflow に投稿するときは、質問の背後にある理由を説明しようとしますが、一般的に、それはまったく役に立ちません。ということで、今回は本題に入ろうと思いました。うまくいかなかったことが判明 XD

残念ながら、私の脳は今行き詰まっているので、目標を達成するために最初に考えたことを正しく説明することさえできないと思います. 多くの場合に適合するアトミック操作と例外セーフなコード作成の適切な解決策を見つけようとしていますが、実際にはそれを処理できません XD これは時間とともに習得するだけの種類のものだと思います.

私はまったく新しい C++ プログラマーで、ゲーム開発に重点を置いています。ゲームエンジンで例外がスローされると、それは実行の終了です。システムはプロセス用にすべてのメモリを解放するので、あちこちで 1 つまたは 2 つのポインターがリークしても問題ありません。サーバー アプリケーションを開発しているので、例外を処理するのが難しいと感じています。例外はサーバーをクラッシュさせることはできないからです。「リクエストをクラッシュさせる」必要があります。

つまり、「お客様、残念ながら開発者はこの状態を予測していませんでしたので、後で試していただく必要があります (ここまでは基本的にゲーム エンジンと同じで、何も修復されていないだけです。プロセス全体ではなく、リクエストのみのコンテキストに分離されます。ただし、すべてが有効な状態のままであるため、パニックにならないでください (ただし、ここに違いの 1 つがあります。プロセスは終了しないため、オペレーティング システムはリソースを解放できません。さらに、これまでの操作を元に戻すには注意が必要です。たとえば、ユーザーのアカウントを完全にロックしたり、サーバーが提供する完全なサービスをロックしたりしないようにする必要があります。 )」。

次回はより良い質問を書くことができるように、どんどんコーディングして問題を書き留めます。今さら質問する気になれませんでした、本当に申し訳ありません。

返信ありがとうございます。私はスタックオーバーフローが本当に好きです。私の質問に対する回答の速さと、あなたの回答がどれだけ啓発的であるかには、本当に驚くべきものがあります。ありがとう。

4

4 に答える 4

4

この方法でそれを行うこともできますが、例外がスローされない場合はクリーンアップする必要があります。これは少し面倒なようです。

boost :: shared_ptrのようなものを使用する場合(このようなものはTR1ライブラリにもあると思います-例としてMSの実装を参照してください)、計画どおりに進んだ場合にクリーンアップする必要があることを忘れることができます。

これを機能させるには、ベクターにboost::shared_ptr < T >インスタンスを受け入れるようにする必要があります。その後、元のインスタンスをクリーンアップするだけで、すべてがうまくいった場合にベクター内のインスタンスが存続します。問題が発生した場合、すべてのboost::shared_ptrインスタンスが破棄され、リークは発生しません。

スマートポインターでは、タスクに適したものを選択することが重要です。この場合、共有所有権(または単純な所有権の譲渡)が目標のように思われるため、ほとんどのプラットフォームでstd::auto_ptrよりも優れた候補があります。

于 2009-11-22T03:41:59.360 に答える
4

一般に、std::auto_ptr関数の引数のタイプとして使用することは、関数がオブジェクトの所有権を取得し、オブジェクトを削除する責任があることを呼び出し元に明確に伝えます。関数がその説明に当てはまる場合auto_ptrは、他の理由に関係なく、必ずそこで使用してください。

于 2009-11-22T03:58:11.687 に答える
1

はい、大丈夫です。 (以下の編集を参照してください。)(jkpは私が見逃したものを見ているかもしれませんが、あなたが言うように、その場合はauto_ptrであるため、「例外がスローされた場合でもクリーンアップする必要はありません」とは思いません。オブジェクトが削除されます。)

ただし、auto_ptrshenanigansを呼び出し元から非表示にする方がよいと思います。

void manage(T *t) {
    std::auto_ptr<T> p(t);  // to delete t in case push_back throws
    vec.push_back(t);
    p.release();
}

編集:私は当初、元の計画を参照して「はい、それで結構です」と書きmanage(auto_ptr<T>)ましたが、試してみたところ、うまくいかないことがわかりました。コンストラクターauto_ptr<T>::auto_ptr(T *)explicitです。コンパイラはmanage(new T)、そのポインタを暗黙的にに変換できないため、書き込みを許可しませんauto_ptrmanage(T *)とにかくフレンドリーなインターフェースです!

于 2009-11-22T03:53:41.837 に答える
1

さらに別の答えを正当化するのに十分な議論が生成されたと思います。

まず、実際の質問に答えるために、はい、所有権の譲渡が発生したときにスマート ポインターによって引数を渡すことは絶対に適切です (そして必要です!)。スマート ポインターを渡すことは、それを達成するための一般的なイディオムです。

void manage(std::auto_ptr<T> t) {
    ...
}
...

// The reader of this code clearly sees ownership transfer.
std::auto_ptr<T> t(new T);
manage(t);

ここで、すべてのスマート ポインターに明示的なコンストラクターがあるのには十分な理由があります。次の関数を考えてみましょう (それがあなたの空想をくすぐるなら、精神的に置き換えstd::auto_ptrてください):boost::shared_ptr

void func(std::auto_ptr<Widget> w, const Gizmo& g) {
    ...
}

暗黙のコンストラクターがある場合std::auto_ptr、突然このコードがコンパイルされます。

func(new Widget(), gizmo);

どうしたの?「Effective C++」の項目 17 からほぼそのまま引用します。

func(new Widget(), a_function_that_throws());

C++ では引数評価の順序が定義されていないため、次の順序で引数を評価することができます: new Widget()a_function_that_throws()std::auto_ptrコンストラクター。関数がスローする場合は、リークがあります。

したがって、解放されるすべてのリソースは、関数に渡されるに、構築時に RAII クラスでラップする必要があります。これは、関数に引数として渡す前に、すべてのスマート ポインターを構築する必要があることを意味します。スマート ポインターを const 参照を使用してコピー構築可能にしたり、暗黙的に構築可能にしたりすると、安全でないコードが助長されます。明示的な構築により、より安全なコードが強制されます。

では、なぜこのようなことをしてはいけないのでしょうか。

void manage(T *ptr)
{
    std::auto_ptr<T> autoPtr(ptr);
    vector.push_back(ptr);
    autoPtr.release();
}

既に述べたように、インターフェイスのイディオムは、私が所有するポインターを渡すことができ、それを削除できることを教えてくれます。だから、私がこれをするのを止めるものは何もありません:

T item;
manage(&t);

// or
manage(&t_class_member);

もちろん、これは悲惨なことです。しかし、あなたは「もちろんインターフェースが何を意味するかは知っていますが、そのように使用することはありません」と言うでしょう。ただし、後で関数に追加の引数を追加したい場合があります。または、誰か (あなたではない、または 3 年後のあなた) がこのコードを見つけたとしても、彼らはあなたと同じようには見えないかもしれません。

  1. この架空の「他の誰か」は、コメントのないヘッダーしか見ていない可能性があり、(当然のことながら) 所有権が譲渡されていないと推測します。
  2. 彼らは、この関数が他のコードでどのように使用されているかを見て、ヘッダーを見ずに使用法を複製する場合があります。
  3. コードのオートコンプリートを使用して関数を呼び出し、コメントや関数を読み取らず、所有権が譲渡されていないと推測する場合があります。
  4. 彼らはあなたの関数をラップする関数を書くかもしれmanageませんが、他の誰かがラッパー関数を使用し、元の関数のドキュメントを見逃すでしょう。
  5. 古いコードがすべてコンパイルされる (そして自動的に安全でなくなる) ように、コードを「拡張」しようとする場合があります。

    void manage(T *t, const std::string tag = some_function_that_throws());
    

ご覧のとおり、スマート ポインターを明示的に構築すると、上記のケースで安全でないコードを記述することがはるかに難しくなります。

したがって、私は、何十年にもわたる C++ の専門知識に逆らって、目に見えて「より良く」「楽しい」API を作成することはお勧めしません。

私の2c。

于 2009-11-25T11:12:20.400 に答える