C++には多くのポインターがありますが、C++プログラミング(特にQtフレームワーク)では5年ほど正直に言うと、古いrawポインターのみを使用します。
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
他にも多くの「スマート」ポインタがあることは知っています。
// shared pointer:
shared_ptr<SomeKindofObject> Object;
// unique pointer:
unique_ptr<SomeKindofObject> Object;
// weak pointer:
weak_ptr<SomeKindofObject> Object;
しかし、私はそれらをどうするか、そして生のポインタと比較してそれらが私に何を提供できるかについて少し考えていません。
たとえば、次のクラスヘッダーがあります。
#ifndef LIBRARY
#define LIBRARY
class LIBRARY
{
public:
// Permanent list that will be updated from time to time where
// each items can be modified everywhere in the code:
QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings;
private:
// Temporary reader that will read something to put in the list
// and be quickly deleted:
QSettings *_reader;
// A dialog that will show something (just for the sake of example):
QDialog *_dialog;
};
#endif
これは明らかに完全ではありませんが、これら3つのポインタのそれぞれについて、それらを「生」のままにしても問題ありませんか、それともより適切なものを使用する必要がありますか?
そして、2回目は、雇用主がコードを読む場合、彼は私が使用するポインターの種類に厳格になるでしょうか?
「生の」ポインタはunmanagedです。つまり、次の行:
_SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
_
... delete
が適切なタイミングで実行されない場合、メモリリークが発生します。
auto_ptr
_これらのケースを最小限に抑えるために、 _std::auto_ptr<>
_ が導入されました。ただし、2011年以前のC++の制限により、_auto_ptr
_でメモリリークが発生するのは非常に簡単です。ただし、次のような限られた場合には十分です。
_void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
_
その最も弱いユースケースの1つはコンテナです。これは、_auto_ptr<>
_のコピーが作成され、古いコピーが注意深くリセットされない場合、コンテナーがポインターを削除してデータを失う可能性があるためです。
unique_ptr
_代わりに、C++ 11は _std::unique_ptr<>
_ を導入しました:
_void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
_
そのような_unique_ptr<>
_は、関数間で渡された場合でも、正しくクリーンアップされます。これは、ポインタの「所有権」を意味的に表すことによってこれを行います-「所有者」はそれをクリーンアップします。これは、コンテナでの使用に最適です。
_std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
_
_auto_ptr<>
_とは異なり、_unique_ptr<>
_はここで正常に動作し、vector
のサイズが変更されても、vector
がバッキングストアをコピーしている間、オブジェクトが誤って削除されることはありません。
shared_ptr
_および_weak_ptr
_確かに_unique_ptr<>
_は便利ですが、コードベースの2つの部分が同じオブジェクトを参照し、ポインターをコピーできるようにしながら、適切なクリーンアップが保証されている場合があります。たとえば、 _std::shared_ptr<>
_ を使用すると、ツリーは次のようになります。
_template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
_
この場合、ルートノードの複数のコピーを保持することもでき、ルートノードのすべてのコピーが破棄されると、ツリーは適切にクリーンアップされます。
これは、各_shared_ptr<>
_がオブジェクトへのポインターだけでなく、同じポインターを参照するすべての_shared_ptr<>
_オブジェクトの参照カウントも保持するため機能します。新しいものが作成されると、カウントは増加します。 1つが破壊されると、カウントは減少します。カウントがゼロに達すると、ポインターはdelete
dになります。
したがって、これにより問題が発生します。二重リンク構造は循環参照になります。 parent
ポインターをツリーに追加したいとしますNode
:
_template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
_
ここで、Node
を削除すると、それへの循環参照があります。参照カウントがゼロになることはないため、delete
dになることはありません。
この問題を解決するには、 _std::weak_ptr<>
_ を使用します。
_template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
_
これで、正常に動作し、ノードを削除しても、親ノードへの参照のスタックは残りません。ただし、ツリーのウォークが少し複雑になります。
_std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
_
このようにして、ノードへの参照をロックすることができます。また、ノードの_shared_ptr<>
_を保持しているため、作業中にノードが消えないことを合理的に保証できます。
make_shared
_および_make_unique
_現在、_shared_ptr<>
_と_unique_ptr<>
_には、対処する必要があるいくつかの小さな問題があります。次の2行に問題があります。
_foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
_
thrower()
が例外をスローすると、両方の行でメモリリークが発生します。それ以上に、_shared_ptr<>
_は、参照先のオブジェクトから遠く離れた参照カウントを保持します。これはcanは2番目の割り当てを意味します)。それは通常望ましくありません。
この問題を解決するために、C++ 11は std::make_shared<>()
を提供し、C++ 14は std::make_unique<>()
を提供します。
_foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
_
どちらの場合でも、thrower()
が例外をスローしても、メモリリークは発生しません。おまけとして、make_shared<>()
は、参照カウントを作成する機会があります同じメモリ空間で管理対象オブジェクトと同じです。これにより、より高速で数バイトのメモリを節約できます。例外的な安全保証を提供しながら!
ただし、C++ 11以前のコンパイラをサポートする必要のあるQtには独自のガベージコレクションモデルがあることに注意してください。多くのQObject
sには、必要なく適切に破棄されるメカニズムがあります。ユーザーにdelete
してもらいます。
C++ 11マネージポインターで管理されている場合のQObject
sの動作がわからないので、_shared_ptr<QDialog>
_が適切であるとは言えません。確かに言うにはQtの十分な経験がありませんが、私はbelieve Qt5がこの使用例に合わせて調整されていることを確信しています。