目次
2.operator new() とoperator delete()
(6.2) get によって返されたポインタは注意して使用してください
1. 動的割り当て
プログラマが new を使用してオブジェクトにメモリを割り当てる (new はオブジェクトへのポインタを返す) 方法は、動的割り当てと呼ばれます。メモリを動的に割り当てるこの方法は、特に新規および削除のアプリケーションの場合、メモリを直接管理することと同等です。
int *p = new int; //*p为一个随机值
この割り当てられた int オブジェクトには実際には名前がないか、名前のないオブジェクトですが、new はオブジェクトへのポインタを返すことができ、このポインタを通じて名前のない int オブジェクトを操作できます。
1. 3つの初期化方法
多くの組み込み型 (int 型など) オブジェクトでは、初期値が積極的に提供されない場合、上記のコードに示すように、初期値は未定義 (不確実) になります。
初期化には、値の初期化 と呼ばれる別の言い方があります。自己定義クラスの場合、そのクラスのオブジェクトが新規作成されるとき、いわゆる値の初期化は無意味です。したがって、int のような組み込み型を持つことは理にかなっています。値の初期化がどのように記述されているかを見てください。
int *p1 = new int();//值初始化,*p为0
もちろん、オブジェクトを新しい状態で初期化することもできます。
int *p2 = new int(100);//new一个对象的同时进行初始化,*p为100
2. メモリを解放する
メモリを削除するには、一度だけ削除できます。複数回削除することはできません。もちろん、NULL ポインターは複数回削除できますが、実際的な意味はありません。
ポインターを削除した後は、ポインターを null (p=nullptr;) に設定することをお勧めします。ポインタを削除しても、ポインタはそのポインタが指す動的メモリのアドレスを保持し続けるため、このときのポインタをダングリングポインタ(ワイルドポインタとも言います。プログラマは、ポインタが指すメモリを操作できなくなります)といいます。次に、ポインタに nullptr を与えると、ポインタがメモリを指さないことを意味しますが、これは良い習慣です。
Const オブジェクトの値は変更できませんが、削除できます。
const int *p = new int(100);
delete p;
p = nullptr;
(1) ぶら下がりポインタ
ダングリング ポインタ (ワイルド ポインタとも呼ばれる) は、解放または削除されたメモリ ブロックへのポインタです。C++ では、これは通常、次の状況で発生します。
1. オブジェクトが削除されたが、1 つ以上のポインターがまだそのオブジェクトを参照している場合。
2. 関数が戻っても、ローカル変数のアドレスは保持されています。
3. 動的に割り当てられたメモリは解放されたが、ポインタ参照がまだ残っている場合。ダングリング ポインターを使用すると、プログラムのクラッシュ、データの破損、予期しない動作が発生する可能性があります。これを回避するには、オブジェクトを削除するかメモリを解放した後、関連するすべてのポインタが nullptr に設定され、ローカル変数のアドレスを返さないようにする必要があります。
int* ptr = new int(5); // 动态分配内存
delete ptr; // 释放内存
ptr = nullptr; // 将ptr设置为nullptr以避免成为悬空指针
C++11 以降では、削除後にスマート ポインターを null に設定すると、ダングリング参照が自動的に防止されることに注意してください。
std::unique_ptr<int> ptr(new int(5)); // 使用智能指针
ptr.reset(); // 释放内存并将ptr设置为空
3. 新しいプロジェクトを作成し、メモリ リークを観察する
「MFC アプリケーション」を使用してメモリ リークを観察できます。「MFC アプリケーション」の特徴は、プログラムの終了時(終了時)に、プログラムにメモリリークがあった場合に、それを報告できることです。
中国語に翻訳された「メモリ リークが検出されました!」という言葉は、メモリ リークが発生したことを示します。そして、リークが 2 つあり、1 つは 8 バイト、もう 1 つは 28 バイト、合計 36 バイトがリークされています。
2. キーワードの新規/削除についての深い理解
1.新規作成と削除
new と delete は両方ともキーワード (演算子/演算子とも呼ばれます) であり、関数ではありません。malloc と free は C 言語プログラミングで使用され、new と delete は C++ プログラミングで使用されます。
new/delete と malloc/free の最も明らかな違いの 1 つは、new を使用してクラス オブジェクトを生成する場合、システムはクラスのコンストラクターを呼び出します。delete を使用してクラス オブジェクトを削除する場合、システムはクラスのデストラクター (解放関数)。コンストラクターとデストラクターを呼び出す機能があるため、new と delete には初期化 (クラスのコンストラクターに初期化コードを置く) および解放 (ヒープによって割り当てられたメモリに解放関連のコードを置く) 機能があることを意味します。 ) (クラスのデストラクター内の) 機能。malloc と free にはありません。
2.operator new() とoperator delete()
演算子 new(…) と演算子 delete(…) は実際には関数です。では、これら 2 つの関数と new/delete 演算子の間にはどのような関係があるのでしょうか?
(1) new 演算子は 2 つのことを行います: ① メモリを割り当てる; ② コンストラクタを呼び出してメモリを初期化します。新しいオペレータはどのようにメモリを割り当てるのでしょうか? new 演算子は、operator new(...) を呼び出してメモリを割り当てます。この関数は直接呼び出すこともできますが、これを行う人はほとんどいません。
(2) delete オペレータは、次の 2 つのことも行います: ① デストラクタを呼び出す; ② メモリを解放する。delete 演算子は、operator delete() を呼び出してメモリを解放します。
3. アレイの申請と解放
配列にメモリを動的に割り当てる場合は、new[...] などの [] を使用する必要があることが多く、配列を解放する場合は、delete[...] などの [] を使用する必要があることがよくあります。 new[] と delete[ がよく使用されます。] を一緒に使用する必要があります。
class My_Class {
public:
My_Class()
{
std::cout << "构造函数"<<std::endl;
}
~My_Class()
{
std::cout << "析构函数"<<std::endl;
}
};
int main()
{
My_Class *myClass = new My_Class[2]; //调用两次构造函数
delete[] myClass; //调用两次析构函数
return 0;
}
この呼び出しにより、クラスのデストラクターが 2 回呼び出されるのはなぜですか? new が実行されたときに、new が生成した配列要素 (クラス オブジェクト) の数をシステムはどのようにして知るのでしょうか?
C++ のアプローチは、配列領域を割り当てるときに、特に配列のサイズを節約するために、さらに 4 バイトを割り当てることです。[] を削除するときに、配列サイズの数値を取り出すことができ、デストラクターの回数がわかりますを呼び出す必要があります。
int 型などの組み込み型の場合、削除時にクラスのデストラクターを呼び出す方法はありません (クラス オブジェクトまたはクラス オブジェクトの配列が削除される場合のみ)。クラスのデストラクターを呼び出す方法はありません。 , したがって、new を使用する場合、システムはあまり何もすることがありません。4 バイトのメモリを割り当てます。
- クラスのデストラクターをコメント アウトし、new[] を使用してオブジェクト配列にメモリを割り当て、個別の削除を使用してメモリを解放する場合、例外は発生しません。
class My_Class {
public:
My_Class()
{
std::cout << "构造函数"<<std::endl;
}
//~My_Class()
//{
// std::cout << "析构函数"<<std::endl;
//}
};
int main()
{
My_Class *myClass = new My_Class[2]; //调用两次构造函数
delete myClass; //没问题
return 0;
}
- クラスが独自のデストラクターを作成し、new[] を使用してオブジェクト配列にメモリを割り当て、個別の削除を使用してメモリを解放すると、例外が報告されます。
class My_Class {
public:
My_Class()
{
std::cout << "构造函数"<<std::endl;
}
~My_Class()
{
std::cout << "析构函数"<<std::endl;
}
};
int main()
{
My_Class *myClass = new My_Class[2]; //调用两次构造函数
delete myClass; //会出异常
return 0;
}
例外が発生する理由は、コード行「delete myClass;」が次の 2 つのことを行うためです。
(1) クラスのデストラクタを 1 回呼び出します。newを使用するとオブジェクトが2つ作成され、コンストラクタが2回呼び出され、解放時にデストラクタが1回呼び出されます。致命的ではありませんが、後遺症(クラスのコンストラクタにメモリが確保されている場合など)もあります。デストラクター内のメモリを解放するために、デストラクターが 1 回未満実行されると、メモリ リークが直接発生します)。
(2) 「operator delete(myClass);」を呼び出してメモリを解放します。システムによって報告される例外は、実際には、このコード行を実行する呼び出しによって発生します。解放されたメモリ空間が乱れているのは、さらに 4 バイトを割り当てるという問題のためです。
したがって、オブジェクトがメモリの割り当てに new[] を使用し、メモリの解放に (delete[] の代わりに) delete を使用する場合、このオブジェクトが満たす条件は次のとおりです。 オブジェクトの型が組み込み型 (int など) type) またはカスタム デストラクターのないクラス タイプ。
3. スマートポインター
オブジェクトを直接新規作成すると、オブジェクトへのポインタが返されますが、多くの人はこのオブジェクトのポインタをベア ポインタと呼びます。いわゆるネイキッドとは、パッケージ化されていないそのままの新品のポインターを指します。明らかに、この種のポインタは強力で柔軟に使用できます。同時に、開発者はプロセス全体を通じてポインタを維持する責任を負わなければなりません。注意しないと間違いを犯しやすくなります。一度誤って使用すると、次のような問題が発生する可能性があります。巨大な。
C++ 標準ライブラリには、std::auto_ptr、std::unique_ptr、std::shared_ptr、std::weak_ptr の 4 種類のスマート ポインターがあります。それぞれに適用可能な場面があり、プログラマーが動的に割り当てられたオブジェクト (新しいオブジェクト) のライフ サイクルを管理できるようにするために存在します。スマート ポインターはオブジェクトのライフ サイクルを管理できるため、メモリ リークを効果的に防ぐことができます。
現在、std::auto_ptr は std::unique_ptr に完全に置き換えられているため、std::auto_ptr はもう使用しないでください。C++11 標準では、std::auto_ptr の使用も非推奨になっています。
auto_ptr の使用には、auto_ptr をコンテナに保存できない、関数から auto_ptr を返すことができないなど、いくつかの制限 (欠点) があります。
- shared_ptr は共有ポインタの概念です。複数のポインタが同じオブジェクトを指しています。最後のポインタが破棄されると、オブジェクトは解放されます。
- スマート ポインタweak_ptrは、shared_ptrの作業を支援するために使用されます。
- unique_ptr は排他的なポインタの概念で、同時にオブジェクトを指すことができるポインタは 1 つだけです。もちろん、オブジェクトの所有権を譲渡することもできます。
1.shared_ptr
shared_ptr ポインタは、共有所有権を使用して、指すオブジェクトの有効期間を管理します。したがって、オブジェクトは特定のshared_ptrによって所有されるだけでなく、複数のshared_ptrによって所有されることもできます。複数のshared_ptrポインタが相互に連携して、ポイントされたオブジェクトが不要になったときに確実に解放されます。
この種のスマート ポインタを使用するかどうかを決定するときは、まず、指定されたオブジェクトを共有する必要があるかどうかを考慮する必要があります。共有する必要がある場合 (複数のポインタが同じメモリを指す必要があるのと同様)、排他的 (このメモリを指すポインタが 1 つだけ) の場合のみ、shared_ptr にはそれほど追加のオーバーヘッドがありませんが、共有のための追加のオーバーヘッドがまだ存在するため、unique_ptr スマート ポインタを使用することをお勧めします。 。
shared_ptr の動作メカニズムは参照カウントを使用することです。各shared_ptr は同じオブジェクト (メモリ) を指しているため、明らかに、オブジェクトを指している最後のshared_ptr ポインタがそのオブジェクトを指さなくなった場合にのみ、shared_ptr は破棄されます。
(1) 従来の初期化
std::shared_ptr<std::string> p1; //默认初始化,智能指针里面保存的是一个空指针nullptr(可以指向类型为string的对象)。
std::shared_ptr<std::string> p2 = new std::string("abc"); //错误,智能指针是被explicit修饰的,不支持隐式转换(带等号一般为隐式转换)
std::string *str = new std::string("abc");
std::shared_ptr<std::string> p3 (str); //虽然裸指针可以初始化智能指针,但是不推荐智能指针和裸指针穿插使用
std::shared_ptr<std::string> p4(new std::string("abc")); //正确
(2)make_shared
make_shared 関数は、標準ライブラリの関数テンプレートであり、shared_ptr スマート ポインターを割り当てて使用するための最も安全で効率的な関数テンプレートであると考えられています。動的メモリ (ヒープ) にオブジェクトを割り当てて初期化し、このオブジェクトを指すshared_ptr を返すことができます。
注: make_shared メソッドを使用してshared_ptr オブジェクトを生成すると、デリーターをカスタマイズする方法はありません。
std::shared_ptr<int> p = std::make_shared<int>(10);
(3) 参照カウント
(3.1) 参照数の増加
各shared_ptrには参照カウントが関連付けられており、以下の状況が発生した場合、このオブジェクトを指すすべてのshared_ptrの参照カウントが1増加します。
- p2 スマート ポインタを p1 スマート ポインタで初期化すると、オブジェクト (メモリ) を指すすべてのshared_ptr 参照カウントが 1 増加します。
std::shared_ptr<A> p2 = std::make_shared<A>(10, 10);
std::shared_ptr<A> p3(p2); //也可写成这样:auto p3(p2);
std::cout << p3.use_count() << std::endl; //2
- スマート ポインタを実パラメータとして関数に渡します (このポインタの参照カウントは関数の実行後に復元されます
)。ただし、参照を仮パラメータとして渡した場合、参照カウントは増加しません。
void Test(std::shared_ptr<int> num)
{
std::cout << num.use_count() << std::endl; //3
}
void main()
{
std::shared_ptr<int> p = std::make_shared<int>(10); //1
auto p1(p);//2
Test(p1); //3
std::cout << p1.use_count() << std::endl; //2
}
void Test(std::shared_ptr<int>& num)
{
std::cout << num.use_count() << std::endl; //2
}
void main()
{
std::shared_ptr<int> p = std::make_shared<int>(10);//1
auto p1(p);//2
Test(p1);
}
- 関数の戻り値として、変数で受け取ります。
std::shared_ptr<int> Test(std::shared_ptr<int>& num) //注意是引用
{
std::cout << num.use_count() << std::endl; //2
return num;
}
void main()
{
std::shared_ptr<int> p = std::make_shared<int>(10);
auto p1(p);
auto p2 = Test(p1);
std::cout << p1.use_count() << std::endl; //3
}
(3.2) 参照カウントの削減
- 新しい値をshared_ptrに割り当て、shared_ptrが新しいオブジェクトを指すようにします。
- ローカルのshared_ptrはそのスコープを離れます。
- shared_ptr の参照カウントが 0 になると、管理しているオブジェクトを自動的に解放します。
(4)shared_ptrポインタに対する共通の操作
(4.1) use_countメンバ関数
このメンバー関数は、オブジェクトを指すスマート ポインターの数を返すために使用されます。このメンバー関数は主にデバッグ目的で使用されるため、効率的ではない可能性があります。
(4.2) 独自メンバ関数
スマート ポインターが排他的にオブジェクトを指すかどうか、つまり、オブジェクトを指すスマート ポインターが 1 つだけある場合、unique は true を返し、それ以外の場合は false を返します。
(4.3) リセットメンバ関数
- リセット時にはパラメータは必要ありません。pi がオブジェクトを指す唯一のポインターである場合、pi が指すオブジェクトを解放し、pi を空にします。オブジェクトを指すポインターが pi だけではない場合、pi が指すオブジェクトは解放されませんが、オブジェクトを指す参照カウントは 1 減らされ、pi は空になります。
- リセット時はパラメータ (通常は new からのポインタ) を受け取ります。pi がオブジェクトを指す唯一のポインタである場合、pi が指すオブジェクトを解放し、pi が新しいメモリを指すようにします。pi がオブジェクトへの唯一のポインタではない場合、pi が指すオブジェクトは解放されませんが、オブジェクトを指す参照カウントは 1 減らされ、pi は新しいメモリを指します。
- Null ポインタは、リセットによって再初期化することもできます。
(4.4) メンバー関数の取得
p.get(): スマート ポインタ p に保存されているポインタを返します。スマート ポインターが指すオブジェクトを解放すると、返されたポインターが指すオブジェクトは無効になります。
なぜそのような機能が必要なのでしょうか? 主に、一部の関数のパラメータには組み込みポインタ (生のポインタ) が必要であるため、組み込みポインタを get を通じて取得し、そのような関数に渡す必要があることを考慮します。ただし、取得したポインタを削除しないように注意してください。削除しないと、予期しない結果が発生します。
(4.5) スワップメンバ関数
2 つのスマート ポインターが指すオブジェクトを交換するために使用されます。もちろん交換なので参照数は変わりません。
(5) カスタムデリーター
スマート ポインターは、指定した時点でポイントしているオブジェクトを自動的に削除できます。デフォルトでは、shared_ptr は、それが指すオブジェクトを削除するデフォルトの方法として delete 演算子を使用します。
プログラマーは独自のデリーターを指定できるため、スマート ポインターが指すオブジェクトを削除する必要がある場合、オブジェクトを削除するためにデフォルトの削除演算子を呼び出すのではなく、プログラマーが提供するデリーターを呼び出して削除するようになります。に指差す。通常の状況では、デフォルトのデリーターは非常にうまく機能しますが、デフォルトのデリーターでは処理できないため、独自のデリーターを指定する必要がある状況もあります ( shared_ptr を使用して動的配列を管理する場合は、独自のデリーターを指定する必要があります)。デフォルト デリーターは配列オブジェクトをサポートしません。
実は、配列を定義する際に、山括弧「<>」の間に「[]」を追加すると、自分でデリータを書かなくても、正常にメモリを解放することができます。
std::shared_ptr<int[]> p4(new int[10]{0});
shared_ptr でのデリーターの指定方法は比較的簡単で、通常はパラメーターに特定のデリーター関数名を追加するだけです (デリーターはパラメーターが 1 つの関数であることに注意してください)。デリーターはラムダ式にすることもできます。
void My_Deleter(int* a)
{
delete a;
}
void main()
{
///自定义删除器 lambda表达式形式
std::shared_ptr<int> p4(new int(10), [](int *p) {
delete p;
});
///自定义删除器 函数指针形式
std::shared_ptr<int> p5(new int(10), My_Deleter);
}
もう 1 つの注目すべき点: 2 つのshared_ptr によって指定されたデリーターが異なっていても、それらが同じオブジェクトを指している限り、2 つのshared_ptr も同じタイプに属します。同じ型の明らかな利点は、要素型がオブジェクト型のコンテナに配置できるため、操作が容易になることです。
std::vector<std::shared_ptr<int>> vec{ p4,p5 };
(6) 使い方の提案、落とし穴、タブー
(6.0) make_shared を使用してスマート ポインターを構築することを優先する
make_shared を使用してスマート ポインターを構築することを優先します。コンパイラはメモリ割り当てのための特別な処理を行うため、重複コードの排除、セキュリティの向上など、make_shared の効率が向上します。
std::shared_ptr<std::string> p1(new std::string("Hello world"));
上記のコード行は少なくとも 2 回メモリを割り当てます。1 回目は文字列型のインスタンスにメモリを割り当て (これにより文字列 "Hello world" が保存されます)、2 回目は Shared_ptr 制御ブロックにメモリを割り当てます。 shared_ptr コンストラクター。
make_shared の場合、コンパイラはメモリを 1 回だけ割り当てます。このメモリ割り当ては、文字列 (「Hello world」) と制御ブロックの両方を同時に保存するのに十分な大きさです。
std::shared_ptr<std::string> p2 = std::make_shared<std::string>("Hello world");
(6.1) 生のポインタは注意して使用してください
通常の raw ポインタがshared_ptr に関連付けられている場合、メモリ管理の責任はshared_ptr に引き継がれますが、この時点では、shared_ptr が指すメモリへのアクセスに raw ポインタ (組み込みポインタ) を使用することはできません。
生のポインターはshared_ptrを初期化できますが、生のポインターを使用して複数のshared_ptrを初期化しないように注意してください。生のポインターを使用する場合でも、生のポインター変数を渡すのではなく、new 演算子を直接渡します。
(6.2) get によって返されたポインタは注意して使用してください
get で取得したポインタを使用して、別のスマート ポインタを初期化したり、別のスマート ポインタに値を割り当てたりしないでください。
(6.3) これを返すには、enable_shared_from_this を使用します。
Enable_shared_from_this はクラス テンプレートであり、その型テンプレート パラメーターはそれを継承するサブクラスのクラス名です。このクラス テンプレートには弱いポインタweak_ptrがあります。この弱いポインタはこれを観察できます。shared_from_thisメソッドが呼び出されるとき、このメソッドは実際にこのweak_ptrのlockメソッドを呼び出します。lockメソッドはshared_ptrポインタのカウントを1つ増やし、共有_ptr. .
(6.4) 相互の循環参照を避ける
相互に循環参照すると、メモリ リークが発生する可能性があります。
(7)移動セマンティクス
shared_ptr スマート ポインターには、移動セマンティクスの概念もあります。コピーすると、shared_ptr の強参照カウントが増加しますが、移動しても、shared_ptr の強参照カウントは増加しません。
void main()
{
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::shared_ptr<int> p2 = std::move(p1);
std::cout << p1.use_count() << std::endl; //0
std::cout << p2.use_count() << std::endl; //1
}
2.weak_ptr
スマート ポインタweak_ptrは、shared_ptrの作業を支援するために使用されます。「弱」を中国語に訳すと「弱い」となりますが、「弱い」と「強い」は対義語ですが、「強い」とは誰のことを指すのでしょうか?強いポインタがshared_ptrであり、弱いポインタがweak_ptrであることは容易に想像できます。
weak_ptr をshared_ptr にバインドしても、shared_ptr の参照カウントは変わりません (より正確には、weak_ptr の構築と破棄によって、ポイントされたオブジェクトの参照カウントが増減することはありません)。shared_ptr が指すオブジェクトを解放する必要がある場合、そのオブジェクトを指すweak_ptr が存在するかどうかに関係なく、通常どおり解放されます。これが、weak が「weak」である理由です。機能が弱く (弱い共有/弱い参照: 他のshared_ptr によってポイントされたオブジェクトを共有する)、ポイントされたオブジェクトの存続期間を制御できません。
弱参照は、shared_ptr(強参照)のライフサイクルを監視するために使用されると理解できますが、shared_ptrを拡張したものであり、独立したスマートポインタではなく、指されたリソースを操作するために使用することはできません。 Shared_ptrのヘルパー(観客)はこんな感じ。したがって、その知能は、それが指すオブジェクトが存在するかどうかを監視できる程度の賢さしかありません。もちろん、その他の用途もあります。
shared_ptr が指すオブジェクトが表す参照はすべて強参照を参照し、weak_ptr が指すオブジェクトが表す参照はすべて弱参照を参照します。プログラマーは、weak_ptr を使用してオブジェクトに直接アクセスすることはできません。lock と呼ばれるメンバー関数を使用する必要があります。lock の機能は、weak_ptr が指すオブジェクトがまだ存在するかどうかを確認することです。存在する場合、lock は共有オブジェクトを指すshared_ptr を返すことができます(もちろん、元のshared_ptrの参照数は+1されますが、存在しない場合は空のshared_ptrが返されます。
std::shared_ptr<int> p6 = std::make_shared<int>(10); // shared_ptr指向的对象代表的引用统统指的都是强引用
std::weak_ptr<int> w_ptr(p6); // weak_ptr所指向的对象代表的引用统统都是弱引用,p6引用计数还是1
auto temp = w_ptr.lock(); //如果w_ptr所指的对象存在,并且有temp接收,p6引用计数加1
if (temp)
{
std::cout << p6.use_count() << std::endl; //2
}
(1)weak_ptrの共通動作
(1.1) use_countメンバ関数
この弱いポインターとオブジェクトを共有する他のshared_ptrsの数を取得するか、現在監視されているリソースの参照カウント(強い参照カウント)を取得します。
(1.2) 期限切れのメンバー関数
ポインタの use_cout が 0 (ウィーク ポインタが指すオブジェクトが存在しないことを示す) の場合は true を返し、それ以外の場合は false を返します。言い換えれば、観察されたオブジェクト (リソース) が解放されたかどうかを判断します。
(1.3) リセットメンバ関数
弱参照ポインターを null に設定しても、オブジェクトを指す強参照の数には影響しませんが、オブジェクトを指す弱参照の数は 1 つ減ります。
(1.4) ロックメンバ関数
監視対象のshared_ptrを取得します。
(2) サイズの問題
weak_ptr のサイズ (つまり、size または sizeof) は、shared_ptr オブジェクトのサイズと同じであり、生のポインターのサイズの 2 倍です。現在の Visual Studio x86 プラットフォームでは、生のポインターの sizeof 値は 4 バイトです。weak_ptr とshared_ptr のサイズは両方とも 8 バイトで、これらの 8 バイトには 2 つの生のポインターが含まれます。
- 最初の生のポインタは、スマート ポインタが指すオブジェクトを指します。
- 2 番目の生ポインタは、大きなデータ構造 (制御ブロック) を指します。この制御ブロックには次のものが含まれます。
- ポイントされたオブジェクトの参照カウント。
- ポイントされたオブジェクトの弱い参照カウント。
- カスタム デリーターのポインター (カスタム デリーターが指定されている場合) などのその他のデータ。
制御ブロックは実際にはshared_ptrによって作成され、shared_ptrオブジェクトを使用してweak_ptrオブジェクトを作成すると、weak_ptrオブジェクトもこの制御ブロックを指します。
(3) 制御ブロック
クラスに続いて制御ブロックが続き、そのサイズは10バイト以上になる場合がありますが、デリータを指定した場合はバイト数が若干大きくなる場合があります。この制御ブロックは、指定されたオブジェクトを指す最初のshared_ptrから作成されます。weak_ptr オブジェクトもshared_ptrを通じて作成されるため、weak_ptrオブジェクトはshared_ptrによって作成されたこの制御ブロックも使用します。
ブロック作成のタイミングを制御します。
- make_shared: オブジェクトを割り当てて初期化し、このオブジェクトを指すshared_ptrを返します。したがって、make_shared は常に制御ブロックを作成します。
- raw ポインターを使用してshared_ptrオブジェクトを作成する場合。(以前にshared_ptrの使用の落とし穴を説明したときに、生のポインタを使用して複数のshared_ptrを初期化すべきではないことを強調しました。そうしないと、複数の制御ブロック、つまり複数の参照カウントが生成され、その結果、ポイントされたオブジェクトが複数回破棄されます。完全な混乱を引き起こします。プログラムが異常に実行されます。)
3.unique_ptr
スマート ポインターの使用に関しては、一般的に、最初に思い浮かび、最初に選択するのは unique_ptr スマート ポインターです。unique_ptr スマート ポインタは排他的スマート ポインタ、または排他的所有権の概念として理解できます。つまり、同時に、このオブジェクト (このメモリ部分) を指す unique_ptr ポインタは 1 つだけ存在できます。この unique_ptr が破棄されると、それが指すオブジェクトも破棄されます。
(1) 従来の初期化
std::unique_ptr<A> p = new A(10,10); //错误,智能指针被explicit修饰,不支持隐式转换(带等号一般为隐式转换)
std::unique_ptr<A> p(new A(10,10)); //正确
A* a = new A(10, 10); //不推荐智能指针和裸指针穿插用
std::unique_ptr<A> p1(a);
(2)メイク_ユニーク
C++11 には make_unique 関数はありませんが、C++14 ではこの関数が提供されています。通常の初期化と比較して、パフォーマンスが高い make_unique 関数を使用することも推奨されます。もちろん、make_shared と同様に、デリーターを使用したい場合は、make_unique 関数を使用することはできません。これは、make_unique がデリーターを指定するための構文をサポートしていないためです。
std::unique_ptr<A> pp = std::make_unique<A>(10, 10); //C++14,推荐
(3) 共通操作
(3.1) 移動セマンティクス
Unique_ptr はコピーや代入などの操作ができず、移動のみが可能でコピーはできないタイプです。std::move を使用して unique_ptr を別の unique_ptr に移動できます。
unique_ptr スマート ポインターはコピーできません。ただし、例外があります。unique_ptr が破棄される場合でも、コピーすることができます。最も一般的なのは、関数から unique_ptr を返すことです。
std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");
std::unique_ptr<std::string> uni_ptr2 = std::move(uni_ptr1);//转移后,uni_ptr1为空
(3.2) メンバー関数の解放
ポインタの制御を放棄し (スマート ポインタとそれが指すオブジェクトとの間の接続を切断し)、ポインタ (ベア ポインタ) を返し、スマート ポインタを空のままにします。返された生のポインターは、手動で削除することによって解放することも、別のスマート ポインターを初期化するか、別のスマート ポインターに値を割り当てるために使用することもできます。
std::unique_ptr<std::string> uni_ptr1 = std::make_unique<std::string>("hello");
std::string *str = uni_ptr1.release();
delete str;
(3.3) リセットメンバ関数
リセットがパラメータを受け取らない場合、スマート ポインタが指すオブジェクトは解放され、スマート ポインタは空になります。リセットがパラメータを受け取ると、スマート ポインタが指すメモリが解放され、スマート ポインタは新しいメモリを指します。
(3.4) メンバー関数の取得
スマート ポインタに保存されているオブジェクト (生のポインタ) を返します。スマート ポインターが指すオブジェクトを解放すると、返されたオブジェクトは無効になるため、使用には注意してください。
(3.5)shared_ptr型への変換
unique_ptr が右辺値の場合は、shared_ptr に割り当てることができます。テンプレートshared_ptrには、右辺値unique_ptrをshared_ptrに変換するために使用できる明示的なコンストラクタが含まれており、元々unique_ptrが所有していたオブジェクトを引き継ぎます。
変換メソッドは、関数を通じて unique_ptr を返し、shared_ptr を使用してそれを受け取ることができます。また、std::move メソッドを使用することもできます。
shared_ptr が作成されると、その内部ポインターは制御ブロックを指します。unique_ptr をshared_ptr に変換すると、システムはこのshared_ptr の制御ブロックも作成します。unique_ptr は制御ブロックを使用しないため、shared_ptr のみが制御ブロックを使用します。
(4) サイズの問題
unique_ptr は基本的に生のポインターと同じであり、十分に小さく、十分に高速に動作します。ただし、デリーターを追加した場合、unique_ptr のサイズが変わらない場合もあれば、変更される場合もあります。
- デリーターがラムダ式などの匿名オブジェクトの場合、unique_ptr のサイズは変わりません。
- デリーターが関数の場合、unique_ptr のサイズが変わります。
unique_ptr のサイズを増やすと効率に一定の影響があることは間違いないため、関数をデリーターとして使用する場合は注意して使用する必要があります。これは、どのデリーターが指定されているかに関係なく、生のポインターのサイズの 2 倍となるshared_ptr とは異なります。
(5)デリーター
unique_ptr のデリーターは比較的複雑なので、最初にタイプ テンプレート パラメーターでデリーターのタイプ名を渡し、次にパラメーターで特定のデリーター名を指定する必要があります。
shared_ptr を作成する場合、デリーターは異なりますが、ポイント タイプ (ポイントされるオブジェクトのタイプ) が同じであるshared_ptr を同じコンテナーに配置できます。しかし unique_ptr に関しては、デリーターが異なると unique_ptr 型全体が異なることを意味するため、異なる型の unique_ptr スマート ポインターを同じコンテナーに入れる方法はありません。
void My_Deleter(int* a)
{
delete a;
a = nullptr;
}
void main()
{
///自定义删除器 lambda表达式形式
using fun = void(*)(int*);
std::unique_ptr<int,fun> p4(new int(10), [](int *p) {
delete p;
p = nullptr;
});
///自定义删除器 函数指针形式
typedef void(*fun)(int*);
std::unique_ptr<int,fun> p5(new int(10), My_Deleter);
}
4. スマートポインタの選択
プログラム内で同じオブジェクトへの複数のポインターを使用する場合は、shared_ptr を選択する必要があります。
プログラムで同じオブジェクトへの複数のポインターが必要ない場合は、unique_ptr を使用できます。
つまり、unique_ptr の使用を優先して選択し、unique_ptr でニーズを満たせない場合は、shared_ptr の使用を検討してください。