web-dev-qa-db-ja.com

同じrawポインタからのC ++複数の一意のポインタ

以下の私のコードを考えてみましょう。一意のポインターについての私の理解は、1つの変数またはオブジェクトを参照するために使用できる一意のポインターは1つだけであるということでした。私のコードでは、同じ変数にアクセスするunique_ptrが複数あります。

ポインターは作成から完全な所有権を持つ必要があるという点で、私が知っているスマートポインターを使用する正しい方法ではないことは明らかです。しかし、それでも、なぜこれが有効でコンパイルエラーがないのですか?ありがとう。

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    int val = 0;
    int* valPtr = &val;

    unique_ptr <int> uniquePtr1(valPtr);
    unique_ptr <int> uniquePtr2(valPtr);

    *uniquePtr1 = 10;
    *uniquePtr2 = 20;

    return 0;
}
10
Engineer999

しかし、それでも、なぜこれが有効なのですか

not有効です! _std::unique_ptr_のデストラクタは自動保存期間でオブジェクトを解放するため、これは未定義の動作です。

実際には、プログラムはintオブジェクトを3回破壊しようとします。最初に_uniquePtr2_を介して、次に_uniquePtr1_を介して、次にval自体を介して。

コンパイルエラーがありませんか?

このようなエラーは通常、コンパイル時に検出できないため、次のようになります。

_unique_ptr <int> uniquePtr1(valPtr);
unique_ptr <int> uniquePtr2(function_with_runtime_input());
_

この例では、function_with_runtime_input()は多くの複雑なランタイム操作を実行し、最終的にはvalPtrが指す同じオブジェクトへのポインターを返します。


_std::unique_ptr_を正しく使用すると、ほとんどの場合_std::make_unique_を使用して、このようなエラーを防ぎます。

5
Christian Hackl

クリスチャンハックルの優れた答えへの単なる追加:

_std::unique_ptr_は、ポインターの [〜#〜] raii [〜#〜] を保証するために導入されました。つまり、生のポインタとは逆に、自分で破壊する必要はもうありません。生のポインターの全体の管理は、スマートポインターによって行われます。 deleteを忘れたために発生したリークはもう発生しません。

_std::unique_ptr_が_std::make_unique_によってのみ作成されることを許可する場合、割り当てと割り当て解除に関しては絶対に安全であり、もちろんコンパイル時にも検出できます。

しかし、そうではありません。_std::unique_ptr_は、生のポインターを使用して構築することもできます。その理由は、ハードポインターを使用して構築できると、_std::unique_ptr_がはるかに便利になるためです。これが不可能な場合、例えばChristianHacklのfunction_with_runtime_input()によって返されるポインターは、最新のRAII環境に統合することはできません。自分で、破壊の世話をする必要があります。

もちろん、これの欠点は、あなたのようなエラーが発生する可能性があることです。_std::unique_ptr_では破壊を忘れることはできませんが、誤った複数の破壊は常に可能です(CHがすでに述べたように、コンパイラで追跡することは不可能です)。生のポインターコンストラクター引数を使用して作成しました。 _std::unique_ptr_は論理的に生のポインタの「所有権」を取得することに常に注意してください。つまり、_std::unique_ptr_自体を除いて他の誰もポインタを削除できないということです。

経験則として、それは言うことができます:

  • 可能であれば、常に_std::unique_ptr_を使用して_std::make_unique_を作成してください。
  • 生のポインターを使用して作成する必要がある場合は、_std::unique_ptr_を作成した後、生のポインターに触れないでください。
  • _std::unique_ptr_が提供された生のポインタの所有権を取得することに常に注意してください
  • ヒープへの生のポインタのみを提供します。ローカルスタック変数を指す生のポインタは絶対に使用しないでください(例のvalのように、それらは不可避的に自動的に破棄されるため)。
  • 可能であれば、newによって作成された生のポインターのみを使用して_std::unique_ptr_を作成します。
  • new以外で作成された生のポインターを使用して_std::unique_ptr_を作成する必要がある場合は、ハードポインターの作成者と一致するカスタム削除ツールを_std::unique_ptr_に追加します。例としては、(Cベースの) FreeImage ライブラリの画像ポインタがあります。これは常にFreeImage_Unload()によって破棄する必要があります。

これらのルールのいくつかの例:

_// Safe
std::unique_ptr<int> p = std::make_unique<int>();

// Safe, but not advisable. No accessible raw pointer exists, but should use make_unique. 
std::unique_ptr<int> p(new int());

// Handle with care. No accessible raw pointer exists, but it has to be sure
// that function_with_runtime_input() allocates the raw pointer with 'new'
std::unique_ptr<int> p( function_with_runtime_input() );

// Safe. No accessible raw pointer exists,
// the raw pointer is created by a library, and has a custom
// deleter to match the library's requirements
struct FreeImageDeleter {
    void operator() (FIBITMAP* _moribund) { FreeImage_Unload(_moribund); }
};
std::unique_ptr<FIBITMAP,FreeImageDeleter> p( FreeImage_Load(...) );

// Dangerous. Your class method gets a raw pointer
// as a parameter. It can not control what happens
// with this raw pointer after the call to MyClass::setMySomething()
// - if the caller deletes it, your'e lost.
void MyClass::setMySomething( MySomething* something ) {
   // m_mySomethingP is a member std::unique_ptr<Something>
   m_mySomethingP = std::move( std::unique_ptr<Something>( something ));
}

// Dangerous. A raw pointer variable exists, which might be erroneously
// deleted multiple times or assigned to a std::unique_ptr multiple times.
// Don't touch iPtr after these lines!
int* iPtr = new int();
std::unique_ptr<int> p(iPtr);

// Wrong (Undefined behaviour) and a direct consequence of the dangerous declaration above.
// A raw pointer is assigned to a std::unique_ptr<int> twice, which means
// that it will be attempted to delete it twice.
// This couldn't have happened if iPtr wouldn't have existed in the first
// place, like shown in the 'safe' examples.
int* iPtr = new int();
std::unique_ptr<int> p(iPtr);
std::unique_ptr<int> p2(iPtr);


// Wrong. (Undefined behaviour)
// An unique pointer gets assigned a raw pointer to a stack variable.
// Erroneous double destruction is the consequence
int val;
int* valPtr = &val;
std::unique_ptr<int> p(valPtr);
_
2
user2328447

このコード例は少し人工的なものです。 unique_ptrは通常、実際のコードではこのように初期化されません。使用する - std::make_unique または初期化unique_ptr生のポインタを変数に格納せずに:

unique_ptr <int> uniquePtr2(new int);
0
ks1322