私はコードを持っています:
std::list<Node *> lst;
//....
Node * node = /* get from somewhere pointer on my node */;
lst.remove(node);
std::list::remove
メソッドは、削除された各要素のデストラクタ (およびメモリの解放) を呼び出しますか? もしそうなら、どうすればそれを避けることができますか?
私はコードを持っています:
std::list<Node *> lst;
//....
Node * node = /* get from somewhere pointer on my node */;
lst.remove(node);
std::list::remove
メソッドは、削除された各要素のデストラクタ (およびメモリの解放) を呼び出しますか? もしそうなら、どうすればそれを避けることができますか?
はい、Foo*
コンテナからを削除すると、は破棄Foo*
されますが、は解放されませんFoo
。生のポインタを破棄することは、常に何の操作もありません。それは他の方法ではありえません!その理由をいくつか挙げさせてください。
ポインターの削除は、ポインターが実際に動的に割り当てられた場合にのみ意味がありますが、ポインター変数が破棄された場合に、ランタイムがそれが当てはまるかどうかをどのように知ることができるでしょうか。ポインターは静的変数と自動変数を指すこともでき、それらの1つを削除すると未定義の動作が発生します。
{
Foo x;
Foo* p = &x;
Foo* q = new Foo;
// Has *q been allocated dynamically?
// (The answer is YES, but the runtime doesn't know that.)
// Has *p been allocated dynamically?
// (The answer is NO, but the runtime doesn't know that.)
}
ポインティが過去にすでに解放されているかどうかを判断する方法はありません。同じポインタを2回削除すると、未定義の動作が発生します。(最初の削除後はダングリングポインタになります。)
{
Foo* p = new Foo;
Foo* q = p;
// Has *q already been released?
// (The answer is NO, but the runtime doesn't know that.)
// (...suppose that pointees WOULD be automatically released...)
// Has *p already been released?
// (The answer WOULD now be YES, but the runtime doesn't know that.)
}
また、ポインタ変数が初期化されているかどうかを検出することもできません。そのようなポインタを削除しようとするとどうなると思いますか?繰り返しますが、答えは未定義動作です。
{
Foo* p;
// Has p been properly initialized?
// (The answer is NO, but the runtime doesn't know that.)
}
Foo*
型システムは、単一のオブジェクトへのポインター()とオブジェクトの配列の最初の要素へのポインター(も)を区別しませんFoo*
。delete
ポインタ変数が破棄されると、ランタイムは、を介して、またはを介してポインタを解放するかどうかを判断できない可能性がありますdelete[]
。間違った形式でリリースすると、未定義の動作が発生します。
{
Foo* p = new Foo;
Foo* q = new Foo[100];
// What should I do, delete q or delete[] q?
// (The answer is delete[] q, but the runtime doesn't know that.)
// What should I do, delete p or delete[] p?
// (The answer is delete p, but the runtime doesn't know that.)
}
ランタイムはポインティに対して意味のあることを何もできないので、ポインタ変数を破棄することは常にノーオペレーションです。何もしないことは、情報に基づいていない推測のために未定義の動作を引き起こすよりも間違いなく優れています:-)
生のポインターの代わりに、コンテナーの値型としてスマートポインターを使用することを検討してください。これは、スマートポインターが、不要になったときにポインターを解放する責任があるためです。必要に応じて、std::shared_ptr<Foo>
またはを使用しますstd::unique_ptr<Foo>
。コンパイラがまだC++0xをサポートしていない場合は、を使用してboost::shared_ptr<Foo>
ください。
繰り返しますが、コンテナの値型として使用することは決してありません。std::auto_ptr<Foo>
list
--の各項目のデストラクタを呼び出しますが、それはNode
オブジェクトではありません。そのNode*
。
Node
したがって、ポインターは削除されません。
それは理にかなっていますか?
リスト内のデータのデストラクタを呼び出します。つまりstd::list<T>::remove
、 のデストラクタが呼び出されます( が のようT
な場合に必要です)。T
std::vector
あなたの場合、Node*
ノーオペレーションである のデストラクタを呼び出します。のデストラクタを呼び出しませんnode
。
はい、ただしこの場合、Node* にはデストラクタがありません。ただし、その内部構造に応じて、さまざまな Node* 値がスコープ ルールによって削除または破棄されます。非基本型の Node* の場合、デストラクタが呼び出されます。
デストラクタはノードで呼び出されますか? いいえ、しかし 'Node' はリスト内の要素タイプではありません。
他の質問については、できません。標準リスト コンテナ (実際にはすべての標準コンテナ) は、コンテンツの所有権を取得し、クリーンアップします。これが発生したくない場合は、標準コンテナーは適切な選択ではありません。
ポインターを に入れているのでstd::list
、デストラクタはポイント先のNode
オブジェクトで呼び出されません。
ヒープに割り当てられたオブジェクトを STL コンテナーに格納し、削除時にそれらを破棄する場合は、それらを次のようなスマート ポインターでラップします。boost::shared_ptr
理解する最善の方法は、各フォームをテストして結果を観察することです。独自のカスタム オブジェクトでコンテナ オブジェクトを巧みに使用するには、動作を十分に理解する必要があります。
つまり、型の場合Node*
、デコンストラクターが呼び出されることも、delete/free が呼び出されることもありません。ただし、タイプNode
の場合、デコンストラクターが呼び出されますが、削除/解放の考慮はリストの実装の詳細です。つまり、リストの実装が new/malloc を使用したかどうかによって異なります。
の場合unique_ptr<Node>
、デコンストラクターが呼び出され、によって割り当てられたものを与える必要があるため、delete/free の呼び出しが発生しますnew
。
#include <iostream>
#include <list>
#include <memory>
using namespace std;
void* operator new(size_t size) {
cout << "new operator with size " << size << endl;
return malloc(size);
}
void operator delete(void *ptr) {
cout << "delete operator for " << ptr << endl;
free(ptr);
}
class Apple {
public:
int id;
Apple() : id(0) { cout << "apple " << this << ":" << this->id << " constructed" << endl; }
Apple(int id) : id(id) { cout << "apple " << this << ":" << this->id << " constructed" << endl; }
~Apple() { cout << "apple " << this << ":" << this->id << " deconstructed" << endl; }
bool operator==(const Apple &right) {
return this->id == right.id;
}
static void* operator new(size_t size) {
cout << "new was called for Apple" << endl;
return malloc(size);
}
static void operator delete(void *ptr) {
cout << "delete was called for Apple" << endl;
free(ptr);
}
/*
The compiler generates one of these and simply assignments
member variable. Think memcpy. It can be disabled by uncommenting
the below requiring the usage of std::move or one can be implemented.
*/
//Apple& operator=(const Apple &from) = delete;
};
int main() {
list<Apple*> a = list<Apple*>();
/* deconstructor not called */
/* memory not released using delete */
cout << "test 1" << endl;
a.push_back(new Apple());
a.pop_back();
/* deconstructor not called */
/* memory not released using delete */
cout << "test 2" << endl;
Apple *b = new Apple();
a.push_back(b);
a.remove(b);
cout << "list size is now " << a.size() << endl;
list<Apple> c = list<Apple>();
cout << "test 3" << endl;
c.push_back(Apple(1)); /* deconstructed after copy by value (memcpy like) */
c.push_back(Apple(2)); /* deconstructed after copy by value (memcpy like) */
/*
the list implementation will call new... but not
call constructor when Apple(2) is pushed; however,
delete will be called; since it was copied by value
in the last push_back call
double deconstructor on object with same data
*/
c.pop_back();
Apple z(10);
/* will remove nothing */
c.remove(z);
cout << "test 4" << endl;
/* Apple(5) will never deconstruct. It was literally overwritten by Apple(1). */
/* Think memcpy... but not exactly. */
z = Apple(1);
/* will remove by matching using the operator== of Apple or default operator== */
c.remove(z);
cout << "test 5" << endl;
list<unique_ptr<Apple>> d = list<unique_ptr<Apple>>();
d.push_back(unique_ptr<Apple>(new Apple()));
d.pop_back();
/* z deconstructs */
return 0;
}
メモリアドレスに注意してください。範囲によって、どれがスタックを指しているか、どれがヒープを指しているかがわかります。