M.6 std::unique_ptr
https://www.learncpp.com/cpp-tutorial/stdunique_ptr/
章の冒頭でpointerの使用がバグやメモリの漏洩を引き起こす方法について説明しました.
たとえば、予期せぬ事前に関数を返すと、ポインタが正しく解放されない可能性があります.
moveの意味の基礎について議論した以上,スマートポインタクラスについて議論しよう.
インテリジェントポインタは、動的に割り当てられたオブジェクトを管理するためのクラスであることに注意してください.
スマートポインタは、他の特性を提供することもできます.
インテリジェントポインタの核心的な特性は、動的に割り当てられたリソースまたはオブジェクトを適切なタイミングで解除することであり、これが主な目標である.
このため、スマートポインタは決して動的に割り当てられません.
stackに割り当てるべきで、スマートポインタで動的割り当てを正しく使用することを保証します.
c++11は4つのスマートポインタを提供します std::auto ptr(使用すべきではなく、c++17で削除) std::唯一のptr(最も一般的なスマートポインタクラス) std::shared_prt std::weak_ptr
std::unique ptrは、std::auto ptrの代わりにc+11で使用されるクラスです.
uniuqe ptrは、複数のオブジェクトではなく1つのオブジェクトにのみ使用する必要があります.
Unique ptrでは、所有権を持つオブジェクトは1つだけです.
他のオブジェクトと所有権を共有できません
std::unique ptrヘッダで定義
簡単な例を見てみましょう.
main関数から離れたscopeは消えます
この時点で自動的に割り当てられたリソースが解除されます
std::auto ptrとは異なり、std::unique ptrはmoveの意味を適切に実現しています.
Resource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed
std::unique ptrはmoveの意味のために設計されているため、レプリカを初期化したり割り当てたりすることはできません.
unique ptrで管理されているコンテンツを移動する場合は、moveの意味を無条件に使用します.
上記のプログラムではstd::moveで実現します
std::moveはres 1をr-valueタイプに変換しています.
これはcopy割り当てではなくmove割り当てです
std::unique ptrはリロードオペレータ*とオペレータ->を実現
これにより、管理中のコンテンツを参照できます.
オペレータ*は、管理リソースの参照を返します.
operator->管理中のリソースを返すポインタ
std::unique ptrは常にobjectを管理しているわけではないことを覚えておいてください.
また、objectがない空の状態でもインスタンス化できることを知っています.
関連するunique ptrに所有権を渡す場合はnullptrを持つこともできます
最初の2つのオペレータを使用する前に、unique ptrが空であるかどうかを確認する必要があります.
幸いなことにstd::unique ptrをboolに変換するとtrueまたはfalseが空であるかどうかがわかります
ここに例があります
出力は次のとおりです.
Resource acquired
I am a resource
Resource destroyed
上記のプログラムでは、演算子*を使用してResourceClass objectを参照します.
オペレータ<<オーバーロードのためcoutを使用して直接出力中
そして、出力でscopeを離れて破壊された様子を確認できます
std::auto ptrとは異なり、std::unique ptrはスマートなクラスであり、スカラーdeleteと配列deleteを使用することができます.
ただし、std::arrayまたはstd::vector(またはstd::string)のほとんどの場合、std::unique ptrはfixedarrayrまたはdynmaic array、c-stylestringと混合して使用するのがより良い選択です.
c++14はstd::make unique()という関数を追加した.
make unique()functionはオプションですが、直接作成や書き込みではなくmake uniqueを使用することをお勧めします.
Use std::make_unique() instead of creating std::unique_ptr and using new yourself.
EXceptionに関係なく、先にスキップ
functionに所有権を持たせるにはstd::unique ptrを使用してbyvalueを通過する必要があります.ちなみにcopyの意味は無効なのでstd::moveを使用してパラメータをr-valueに設定する必要があります
これにより、paramterresを介して所有権を渡すことができます.
したがって,関数のscopeが終わるにつれてResourceは破壊される.
従って、出力は以下のようになる
Resource acquired
I am a resource
Resource destroyed
Ending program
しかし、ほとんどの場合、関数にリソースの所有権を持たせません.
passbyref転送は使用できますが、関数でリソースを変更する場合にのみpassbyrefを使用します.
そうでなければ、resource自体をrefまたはpointerに渡すのがより良い選択です.
これにより、functionは呼び出し者にリソースの管理方法を知らせることができます.
std::unique ptrのraw pointerを購入したい場合はget()というメンバー関数を使用できます
出力は次のとおりです.
Resource acquired
I am a resource
Ending program
Resource destroyed
もちろん、std::unique ptrを設計したクラスのメンバーとして使用できます.
この場合、クラス構造関数がダイナミックメモリを解放する心配はありません.std::unique ptrはクラスオブジェクトです.クラスが破壊されると自然に一緒に破壊されるからです.しかし,我々が設計したclass objectが動的に割り当てられると,当然異なる物語がある.
unique ptrを使用するには2つのエラーがあります
第一に、複数の唯一のptrに同じリソースを管理させないでください.
第二に、std::unique ptr objectを手動で削除しないでください.
章の冒頭でpointerの使用がバグやメモリの漏洩を引き起こす方法について説明しました.
たとえば、予期せぬ事前に関数を返すと、ポインタが正しく解放されない可能性があります.
#include <iostream>
void someFunction()
{
auto* ptr{ new Resource() };
int x{};
std::cout << "Enter an integer: ";
std::cin >> x;
if (x == 0)
throw 0; // the function returns early, and ptr won’t be deleted!
// do stuff with ptr here
delete ptr;
}
上記のfunctionでは、functionscopeを超えてptrが無効になっていないためメモリが漏洩するmoveの意味の基礎について議論した以上,スマートポインタクラスについて議論しよう.
インテリジェントポインタは、動的に割り当てられたオブジェクトを管理するためのクラスであることに注意してください.
スマートポインタは、他の特性を提供することもできます.
インテリジェントポインタの核心的な特性は、動的に割り当てられたリソースまたはオブジェクトを適切なタイミングで解除することであり、これが主な目標である.
このため、スマートポインタは決して動的に割り当てられません.
stackに割り当てるべきで、スマートポインタで動的割り当てを正しく使用することを保証します.
c++11は4つのスマートポインタを提供します
std::unique_ptr
std::unique ptrは、std::auto ptrの代わりにc+11で使用されるクラスです.
uniuqe ptrは、複数のオブジェクトではなく1つのオブジェクトにのみ使用する必要があります.
Unique ptrでは、所有権を持つオブジェクトは1つだけです.
他のオブジェクトと所有権を共有できません
std::unique ptr
簡単な例を見てみましょう.
#include <iostream>
#include <memory> // for std::unique_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
// allocate a Resource object and have it owned by std::unique_ptr
std::unique_ptr<Resource> res{ new Resource() };
return 0;
} // res goes out of scope here, and the allocated Resource is destroyed
上記のプログラムではstd::unique ptrタイプのresがstackに割り当てられます.main関数から離れたscopeは消えます
この時点で自動的に割り当てられたリソースが解除されます
std::auto ptrとは異なり、std::unique ptrはmoveの意味を適切に実現しています.
#include <iostream>
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
};
int main()
{
std::unique_ptr<Resource> res1{ new Resource{} }; // Resource created here
std::unique_ptr<Resource> res2{}; // Start as nullptr
std::cout << "res1 is " << (static_cast<bool>(res1) ? "not null\n" : "null\n");
std::cout << "res2 is " << (static_cast<bool>(res2) ? "not null\n" : "null\n");
// res2 = res1; // Won't compile: copy assignment is disabled
res2 = std::move(res1); // res2 assumes ownership, res1 is set to null
std::cout << "Ownership transferred\n";
std::cout << "res1 is " << (static_cast<bool>(res1) ? "not null\n" : "null\n");
std::cout << "res2 is " << (static_cast<bool>(res2) ? "not null\n" : "null\n");
return 0;
} // Resource destroyed here when res2 goes out of scope
上位プログラムは以下の出力を与えるResource acquired
res1 is not null
res2 is null
Ownership transferred
res1 is null
res2 is not null
Resource destroyed
std::unique ptrはmoveの意味のために設計されているため、レプリカを初期化したり割り当てたりすることはできません.
unique ptrで管理されているコンテンツを移動する場合は、moveの意味を無条件に使用します.
上記のプログラムではstd::moveで実現します
std::moveはres 1をr-valueタイプに変換しています.
これはcopy割り当てではなくmove割り当てです
Accessing the managed object
std::unique ptrはリロードオペレータ*とオペレータ->を実現
これにより、管理中のコンテンツを参照できます.
オペレータ*は、管理リソースの参照を返します.
operator->管理中のリソースを返すポインタ
std::unique ptrは常にobjectを管理しているわけではないことを覚えておいてください.
また、objectがない空の状態でもインスタンス化できることを知っています.
関連するunique ptrに所有権を渡す場合はnullptrを持つこともできます
最初の2つのオペレータを使用する前に、unique ptrが空であるかどうかを確認する必要があります.
幸いなことにstd::unique ptrをboolに変換するとtrueまたはfalseが空であるかどうかがわかります
ここに例があります
#include <iostream>
#include <memory> // for std::unique_ptr
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource\n";
return out;
}
};
int main()
{
std::unique_ptr<Resource> res{ new Resource{} };
if (res) // use implicit cast to bool to ensure res contains a Resource
std::cout << *res << '\n'; // print the Resource that res is owning
return 0;
}
上記のプログラムではif(res)暗黙type castingを用いてチェック出力は次のとおりです.
Resource acquired
I am a resource
Resource destroyed
上記のプログラムでは、演算子*を使用してResourceClass objectを参照します.
オペレータ<<オーバーロードのためcoutを使用して直接出力中
そして、出力でscopeを離れて破壊された様子を確認できます
std::unique_ptr and arrays
std::auto ptrとは異なり、std::unique ptrはスマートなクラスであり、スカラーdeleteと配列deleteを使用することができます.
ただし、std::arrayまたはstd::vector(またはstd::string)のほとんどの場合、std::unique ptrはfixedarrayrまたはdynmaic array、c-stylestringと混合して使用するのがより良い選択です.
c++14はstd::make unique()という関数を追加した.
#include <memory> // for std::unique_ptr and std::make_unique
#include <iostream>
class Fraction
{
private:
int m_numerator{ 0 };
int m_denominator{ 1 };
public:
Fraction(int numerator = 0, int denominator = 1) :
m_numerator{ numerator }, m_denominator{ denominator }
{
}
friend std::ostream& operator<<(std::ostream& out, const Fraction &f1)
{
out << f1.m_numerator << '/' << f1.m_denominator;
return out;
}
};
int main()
{
// Create a single dynamically allocated Fraction with numerator 3 and denominator 5
// We can also use automatic type deduction to good effect here
auto f1{ std::make_unique<Fraction>(3, 5) };
std::cout << *f1 << '\n';
// Create a dynamically allocated array of Fractions of length 4
auto f2{ std::make_unique<Fraction[]>(4) };
std::cout << f2[0] << '\n';
return 0;
}
上記のように使用可能make unique()functionはオプションですが、直接作成や書き込みではなくmake uniqueを使用することをお勧めします.
Best practice
Use std::make_unique() instead of creating std::unique_ptr and using new yourself.
The exception safety issue in more detail
EXceptionに関係なく、先にスキップ
Passing std::unique_ptr to a function
functionに所有権を持たせるにはstd::unique ptrを使用してbyvalueを通過する必要があります.ちなみにcopyの意味は無効なのでstd::moveを使用してパラメータをr-valueに設定する必要があります
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource\n";
return out;
}
};
void takeOwnership(std::unique_ptr<Resource> res)
{
if (res)
std::cout << *res << '\n';
} // the Resource is destroyed here
int main()
{
auto ptr{ std::make_unique<Resource>() };
// takeOwnership(ptr); // This doesn't work, need to use move semantics
takeOwnership(std::move(ptr)); // ok: use move semantics
std::cout << "Ending program\n";
return 0;
}
上のプログラムからstd::moveでptrをr-valueに生成しbyvalueを渡すこれにより、paramterresを介して所有権を渡すことができます.
したがって,関数のscopeが終わるにつれてResourceは破壊される.
従って、出力は以下のようになる
Resource acquired
I am a resource
Resource destroyed
Ending program
しかし、ほとんどの場合、関数にリソースの所有権を持たせません.
passbyref転送は使用できますが、関数でリソースを変更する場合にのみpassbyrefを使用します.
そうでなければ、resource自体をrefまたはpointerに渡すのがより良い選択です.
これにより、functionは呼び出し者にリソースの管理方法を知らせることができます.
std::unique ptrのraw pointerを購入したい場合はget()というメンバー関数を使用できます
#include <memory> // for std::unique_ptr
#include <iostream>
class Resource
{
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource destroyed\n"; }
friend std::ostream& operator<<(std::ostream& out, const Resource &res)
{
out << "I am a resource\n";
return out;
}
};
// The function only uses the resource, so we'll accept a pointer to the resource, not a reference to the whole std::unique_ptr<Resource>
void useResource(Resource* res)
{
if (res)
std::cout << *res << '\n';
}
int main()
{
auto ptr{ std::make_unique<Resource>() };
useResource(ptr.get()); // note: get() used here to get a pointer to the Resource
std::cout << "Ending program\n";
return 0;
} // The Resource is destroyed here
したがって,上記のプログラムはresourceのポインタのみを渡す.出力は次のとおりです.
Resource acquired
I am a resource
Ending program
Resource destroyed
std::unique_ptr and classes
もちろん、std::unique ptrを設計したクラスのメンバーとして使用できます.
この場合、クラス構造関数がダイナミックメモリを解放する心配はありません.std::unique ptrはクラスオブジェクトです.クラスが破壊されると自然に一緒に破壊されるからです.しかし,我々が設計したclass objectが動的に割り当てられると,当然異なる物語がある.
Misusing std::unique_ptr
unique ptrを使用するには2つのエラーがあります
第一に、複数の唯一のptrに同じリソースを管理させないでください.
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
std::unique_ptr<Resource> res2{ res };
上記の場合、実行できますが、プログラムの最後にresを2回無効にしようとしたため、問題が発生しました.第二に、std::unique ptr objectを手動で削除しないでください.
Resource* res{ new Resource() };
std::unique_ptr<Resource> res1{ res };
delete res;
そうすれば、同じように復讐解除問題が発生する.Reference
この問題について(M.6 std::unique_ptr), 我々は、より多くの情報をここで見つけました https://velog.io/@ikmy0ung/M.6-stduniqueptrテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol