まず、スマートポインターを使用します。これについて心配する必要はありません。
次のコードの問題は何ですか?
Foo * p = new Foo;
// (use p)
delete p;
p = NULL;
これは、別の質問に対する 回答とコメント によって引き起こされました。 Neil Butterworth からのコメントの1つは、いくつかの賛成票を生成しました。
削除後にポインターをNULLに設定することは、C++の一般的な推奨事項ではありません。良いことがある場合もあれば、無意味でエラーを隠すことができる場合もあります。
それが役に立たない多くの状況があります。しかし、私の経験では、それは害はありません。誰かが私を啓発します。
ポインターを0に設定すると(標準C++では "null"になり、CからのNULL定義は多少異なります)、二重削除でのクラッシュを回避できます。
以下を考慮してください。
Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything
一方、
Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior
つまり、削除されたポインターを0に設定しないと、二重削除を実行している場合に問題が発生します。削除後にポインターを0に設定することに対する議論は、そうすることで二重削除バグをマスクし、未処理のままにするということです。
明らかに、二重削除のバグを持たないことが最善ですが、所有権のセマンティクスとオブジェクトのライフサイクルによっては、これを実際に達成するのが難しい場合があります。私はUBよりもマスクされた二重削除バグを好む。
最後に、オブジェクト割り当ての管理に関する補足事項として、std::unique_ptr
厳密/単一所有権の場合、std::shared_ptr
必要に応じて、共有所有権、または別のスマートポインター実装用。
指摘したものを削除した後にポインターをNULLに設定しても、確かに害はありませんが、多くの場合、より根本的な問題に対するちょっとした助けになります。私は2つの典型的な理由を見ることができます:
std::vector
は機能し、割り当て解除されたメモリへのポインタを誤って残してしまう問題を解決します。ポインターはありません。new
から返されるポインターは、delete
が呼び出されるポインターとは異なる場合があります。その間に複数のオブジェクトが同時にオブジェクトを使用した可能性があります。その場合、共有ポインターまたは類似のものが望ましいでしょう。私の経験則では、ユーザーコード内にポインターを置いておくと、間違ったことをしていることになります。ポインタは、そもそもゴミを指すためにあるべきではありません。なぜオブジェクトの有効性を保証する責任を負わないのですか?指示先のオブジェクトが終了してもスコープが終了しないのはなぜですか?
さらに良いベストプラクティスがあります。可能な場合は、変数のスコープを終了します。
{
Foo* pFoo = new Foo;
// use pFoo
delete pFoo;
}
指すオブジェクトを削除した後は、常にポインターをNULL
(現在はnullptr
)に設定します。
これは、解放されたメモリへの多くの参照をキャッチするのに役立ちます(nullポインタのderefでのプラットフォームの障害を想定)。
たとえば、ポインタのコピーが横たわっている場合、解放されたメモリへのすべての参照をキャッチしません。しかし、何もよりも優れているものもあります。
それは二重削除をマスクしますが、すでに解放されたメモリへのアクセスよりもはるかに一般的ではないことがわかります。
多くの場合、コンパイラはそれを最適化します。したがって、それが不要であるという議論は私を説得しません。
既にRAIIを使用している場合は、コードにdelete
sが最初から多くないため、追加の割り当てによって混乱が生じるという議論は私を説得しません。
デバッグ時に、古いポインタではなくnull値を確認するのが便利な場合がよくあります。
それでも気になる場合は、代わりにスマートポインターまたは参照を使用してください。
また、リソースが解放されたときに他のタイプのリソースハンドルをno-resource値に設定します(通常は、リソースをカプセル化するために記述されたRAIIラッパーのデストラクタにのみあります)。
私は(主にCで)大規模な(900万の声明)商業製品に取り組みました。ある時点で、マクロマジックを使用して、メモリが解放されたときにポインターを無効にしました。これにより、すぐに修正された多くの潜在的なバグがすぐに公開されました。覚えている限りでは、二重のバグは一度もありませんでした。
更新:マイクロソフトは、これがセキュリティの優れたプラクティスであると考えており、SDLポリシーのプラクティスを推奨しています。どうやらMSVC++ 11は、/ SDLオプションを指定してコンパイルすると、 削除されたポインターをストンプ 自動的に(多くの状況で)します。
まず、このトピックと密接に関連するトピックに関する多くの既存の質問があります。たとえば、 削除しないのにポインターをNULLに設定しないのはなぜですか です。
あなたのコードでは、何が起こっているのか(pを使用)。たとえば、どこかに次のようなコードがある場合:
Foo * p2 = p;
pをNULLに設定しても、ポインターp2がまだ心配なので、ほとんど何もしません。
これは、ポインターをNULLに設定することが常に無意味であると言うことではありません。たとえば、pがライフタイムがpを含むクラスとまったく同じではないリソースを指すメンバー変数である場合、pをNULLに設定することは、リソースの有無を示す便利な方法です。
delete
の後にさらにコードがある場合、はい。コンストラクターで、またはメソッドまたは関数の最後でポインターが削除された場合、いいえ.
このたとえ話のポイントは、実行時にオブジェクトがすでに削除されていることをプログラマに思い出させることです。
さらに良い方法は、ターゲットオブジェクトを自動的に削除するスマートポインター(共有またはスコープ付き)を使用することです。
他の人が言ったように、_delete ptr; ptr = 0;
_は悪魔をあなたの鼻から飛び出させません。ただし、ptr
を並べ替えのフラグとして使用することは推奨されます。コードにはdelete
が散らばり、ポインターをNULL
に設定します。次のステップは、コードでif (arg == NULL) return;
を散布して、NULL
ポインターの偶発的な使用から保護することです。この問題は、NULL
に対するチェックがオブジェクトまたはプログラムの状態をチェックする主な手段になると発生します。
私はどこかでフラグとしてポインタを使用することについてコードの匂いがあると確信していますが、私はそれを見つけていません。
ポインターを削除した後、ポインターをNULLに設定するかどうかを強制する他の制約がない場合(そのような制約の1つは Neil Butterworth によって言及されました)、私の個人的な好みはそれを残すことです。
私にとって、質問は「これは良いアイデアですか?」ではありません。しかし、「これを行うことで、どのような動作を防止または成功させることができますか?」たとえば、これにより他のコードがポインターが利用できなくなったことを確認できる場合、他のコードが解放されたポインターを解放後も参照しようとするのはなぜですか?通常、それはバグです。
また、必要以上に多くの作業を行い、事後デバッグを妨げます。不要になった後、メモリに触れる回数が少ないほど、何かがクラッシュした理由を見つけやすくなります。多くの場合、メモリは特定のバグが発生したときと同様の状態にあるという事実に依存し、そのバグを診断して修正しました。
削除後に明示的にnullにすることは、ポインターが概念的に何かを表すことを読者に強く示唆します オプション。それが行われているのを見た場合、ソースのどこでもポインターが使用されるので、最初にNULLに対してテストする必要があると心配し始めます。
それが実際にあなたが意味するものである場合、 boost :: optional のようなものを使用してソースで明示的にする方が良い
optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();
しかし、ポインターが「悪くなった」ことを本当に人々に知ってほしかったなら、私は、最善を尽くすことは、それを範囲外にすることであると言う人々と100%同意する。次に、コンパイラを使用して、実行時の不正な逆参照の可能性を防ぎます。
それはすべてのC++の入浴剤の赤ちゃんです。捨ててはいけません。 :)
質問を少し変更します。
初期化されていないポインターを使用しますか? NULLに設定していないか、それが指すメモリを割り当てていませんか?
NULLへのポインターの設定をスキップできる2つのシナリオがあります。
一方、ポインターをNULLに設定するとエラーが隠される可能性があると主張するのは、修正によって別のバグが隠される可能性があるため、バグを修正すべきではないと主張するように聞こえます。ポインターがNULLに設定されていない場合に表示される可能性がある唯一のバグは、ポインターを使用しようとするバグです。しかし、実際にNULLに設定すると、解放されたメモリで使用した場合とまったく同じバグが実際に発生しますよね?
適切なエラーチェックを備えた適切に構造化されたプログラムでは、nullを割り当てる理由はありませんnot。 0
は、このコンテキストで広く認識されている無効な値として単独で使用されます。ハードに失敗し、すぐに失敗します。
0
の割り当てに対する多くの引数は、それがcouldバグを隠すか、制御フローを複雑にすることを示唆しています。基本的に、それはアップストリームエラー(あなたのせい(悪い駄洒落のごめん)ではない)か、プログラマーの代わりの別のエラーのいずれかです-プログラムフローが複雑になりすぎていることを示しているかもしれません。
プログラマーが特別な値としてnullの可能性のあるポインターの使用を導入し、その周りに必要な回避をすべて記述したい場合、それは彼らが意図的に導入した複雑さです。検疫が優れていればいるほど、誤用のケースを早く見つけることができ、他のプログラムに広がる可能性が少なくなります。
これらのケースを回避するために、C++機能を使用して適切に構造化されたプログラムを設計できます。参照を使用することも、「nullまたは無効な引数を渡す/使用することはエラーである」と言うこともできます。これは、スマートポインターなどのコンテナーにも同様に適用できるアプローチです。一貫性のある正しい動作を増やすことで、これらのバグが大きくなることを禁止します。
そこからは、NULLポインターが存在する可能性がある(または許可されている)非常に限られたスコープとコンテキストしかありません。
const
ではないポインターにも同じことが適用されます。ポインターの値を追跡することは、スコープが非常に小さく、不適切な使用がチェックされ、適切に定義されているため、簡単です。ツールセットとエンジニアがクイックリードを実行してもプログラムを追跡できない場合、または不適切なエラーチェックまたは一貫性のない/ゆるやかなプログラムフローがある場合、他の大きな問題が発生します。
最後に、コンパイラーと環境には、エラー(スクリブリング)を導入したり、解放されたメモリーへのアクセスを検出したり、他の関連するUBをキャッチしたりする場合のガードがあります。多くの場合、既存のプログラムに影響を与えることなく、同様の診断をプログラムに導入することもできます。
「良いことがある場合もあれば、無意味でエラーを隠すことができる場合もあります」
私は2つの問題を見ることができます:その簡単なコード:
delete myObj;
myobj = 0
マルチスレッド環境のfor-linerになります:
lock(myObjMutex);
delete myObj;
myobj = 0
unlock(myObjMutex);
ドンノイフェルドの「ベストプラクティス」は常に適用されるわけではありません。例えば。ある自動車プロジェクトでは、デストラクタでもポインタを0に設定する必要がありました。安全重視のソフトウェアでは、このようなルールは珍しくありません。コードで使用するポインターごとにチーム/コードチェッカーを説得しようとするよりも、それらに従うことは簡単です(そして賢明です)。このポインターを無効にする行は冗長です。
別の危険性は、例外を使用するコードでこの手法に依存していることです。
try{
delete myObj; //exception in destructor
myObj=0
}
catch
{
//myObj=0; <- possibly resource-leak
}
if (myObj)
// use myObj <--undefined behaviour
このようなコードでは、リソースリークを生成して問題を延期するか、プロセスがクラッシュします。
したがって、この2つの問題は頭を突破して自然に発生します(Herb Sutterがもっと詳しく説明します)が、「スマートポインターの使用を避け、通常のポインターで安全に仕事をする方法」という種類のすべての質問を時代遅れにしています。
あなたがすでに質問に入れたものを拡大させてください。
質問に入れたものを箇条書き形式で示します。
削除後にポインターをNULLに設定することは、C++の一般的な推奨事項ではありません。次の場合があります。
ただし、badの場合はno timesがあります! not明示的にnullにすることでさらにバグを導入し、leakメモリ、ndefined behaviourが発生しないようにします。
したがって、疑わしい場合は、nullにします。
そうは言っても、ポインタを明示的にnullに設定する必要があると感じた場合は、メソッドを十分に分割していないように思えます。メソッドを分割する「メソッドの抽出」と呼ばれるリファクタリングアプローチを見てください別の部品。
はい。
それができる唯一の「害」は、プログラムに非効率性(不必要なストア操作)を導入することですが、ほとんどの場合、このオーバーヘッドはメモリブロックの割り当てと解放のコストに関連して重要ではありません。
そうしないと、いつかポインターの逆参照バグが発生します。
削除には常にマクロを使用します。
#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }
(および配列、free()、ハンドルを解放する場合も同様)
また、呼び出し元コードのポインターへの参照を取得する「自己削除」メソッドを記述して、呼び出し元コードのポインターを強制的にNULLにすることもできます。たとえば、多くのオブジェクトのサブツリーを削除するには:
static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
if (rootObject == NULL)
return;
rootObject->UnlinkFromParent();
for (int i = 0; i < numChildren)
DeleteSubtree(rootObject->child[i]);
delete rootObject;
rootObject = NULL;
}
編集
はい、これらのテクニックはマクロの使用に関するいくつかのルールに違反します(そして、最近、おそらくテンプレートで同じ結果を達成できる可能性があります)-しかし、長年にわたって使用することで決してデッドメモリにアクセスしました-one直面する可能性のある問題をデバッグするのに最も厄介で、最も難しく、最も時間がかかる。実際には、長年にわたって、彼らは私がそれらを導入したすべてのチームから、簡単なクラスのバグを効果的に排除しました。
上記を実装できる多くの方法もあります-呼び出し元のポインターをNULLにしないメモリを解放する手段を提供するのではなく、オブジェクトを削除するとポインターをNULLに強制するという考えを説明しようとしています。
もちろん、上記の例は自動ポインタへの単なるステップです。 OPが自動ポインタを使用しない場合について具体的に尋ねていたので、私は提案しませんでした。
コードがアプリケーションの最もパフォーマンスが重要な部分に属さない場合は、コードをシンプルに保ち、shared_ptrを使用します。
shared_ptr<Foo> p(new Foo);
//No more need to call delete
参照カウントを実行し、スレッドセーフです。 tr1(std :: tr1名前空間、#include <memory>)で見つけることができます。コンパイラが提供していない場合は、boostから取得してください。
ダングリングポインター が常にあります。
ポインターを再度使用する前に再割り当て(参照解除、関数に渡すなど)する場合、ポインターをNULLにすることは単なる追加操作です。ただし、再使用する前に再割り当てするかどうかわからない場合は、NULLに設定することをお勧めします。
多くの人が言っているように、もちろんスマートポインターを使用する方がはるかに簡単です。
編集:Thomas Matthewsが この以前の回答 で述べたように、デストラクタでポインタが削除された場合、オブジェクトがすでに破壊されています。
ポインターを削除した後にNULLに設定すると、単一の関数(またはオブジェクト)で再利用する正当なシナリオがあるまれなケースで役に立つことが想像できます。それ以外の場合は意味がありません-ポインターは、存在する限り意味のあるものを指す必要があります-期間。