Effective Modern C++ - C++11/14 プログラムを進化させる42項目 Scott Meyers O'reilly Japan ISBN978-4-87311-736-2 C++/C++14/SmartPointer †C++11のスマートポインタ
項目18: 独占するリソースの管理には std::unique_ptr を用いる †unique_ptr のサイズは、デフォルトでは raw ポインタと同じであり、多くの演算で raw ポインタと同じ命令を実行する と想定して構わない。 std::unique_ptr は exclusive ownership (排他的所有権)セマンティクスを備えている。 std::unique_ptr は指しているデータがnull以外の場合はそれを所有しており、 unique_ptr をmoveすると所有権もmove先に移る(その場合は元のポインタはnullになる)。 すなわち unique_ptr は move-only type でありコピーはできない。 unique_ptr を破棄すると対象のリソースも破棄する(デフォルトのリソース破棄は raw ポインタに対して delete を行う)。 class Investment { ... }; class Stock: public Investment { ... }; class Bond: public Investment { ... }; class RealEsatte: public Investment { ... }; template<typename... Ts> std::unique_ptr<Investment> // return type makeInvestment(Ts&&... params); 呼び出し側 { ... auto pInvestment = makeInvestment( arguments ); // std::unique_ptr<Investment> ... } // destory *pInvestment デフォルトでは delete いおり破棄されるが、custom deleter を用いるように std::unique_ptr 作成時に指定できる。 auto delInvmt = [](Investment* pInvestment) { makeLogEntry(pInvenstment); delete pInvestment; } template<typename... Ts> std::unique_ptr<Investment, decltype(delInvmt)> // return type makeInvestment(Ts&&... params) { std::unique_ptr<Investment, decltype(delInvmt)> pInv(null, delInvmt); // ptr to be returned if (/* a Stock object should be created */ ) { pInv.reset(new Stock(std::forward<Ts>(params)...)); } else if (/* a Bond object should be created */ ) { pInv.reset(new Bond(std::forward<Ts>(params)...)); } else if (/* a RealEstate object should be created */ ) { pInv.reset(new RealEstate(std::forward<Ts>(params)...)); } return pInv; }
C++14 には関数の戻り型を推論する機能があるため、makeInvestment を簡潔にカプセル化できる。 tmplate<typename... Ts> auto makeInvestment(Ts&&... params) { auto delInvmt = [](Investment* pInvestment) { makeLogEntry(pInvestment); delete pInvestment; }; std::unique_ptr<Investment, decltype(delInvmt)> pInv(nullptr, delInvmt); if ( ... ) { pInv.reset(new Stock(std::forward<Ts>(params)...)); } else if ( ... ) { pInv.reset(new Bond(std::forward<Ts>(params)...)); } else if ( ... ) { pInv.reset(new RealEstate(std::forward<Ts>(params)...)); } return pInv; } デフォルトの deleter を使用する場合は std::unique_ptr のサイズが raw ポインタと同じであると想定しても安全である。 しかし、custom deleter を用いる場合は、1 or 2ワードサイズが増加するのが一般的である。 また、関数オブジェクトを与えると、関数オブジェクトが保持する状態のサイズに応じて増加する。 関数オブジェクトが状態を持たなければサイズは変化しないので、custom deleter はキャプチャを伴わないラムダ式で記述する方が望ましい。 auto delInvmt1 = [](Investment* pInvestment) { // custom deleter makeLogEntry(pInvestment); // as stateless delete pInvestment; // labmda }; template<typename... Ts> // return type std::unique_ptr<Investment, decltype(delInvmt1)> // has size of makeInvestment(Ts&&... args); // Investment* void delInvmt2(Investment* pInvestment) { // custom deleter makeLogEntry(pInvestment); // as function delete pInvestment; } template<typename... Ts> // return type has size of Investment* std::unique_ptr<Investment, void (*)(Investment*)> // plus at least size of makeInvestment(Ts&&... params); // function pointer } std::unique_ptr には2つの形式がある。
重要ポイント †
項目19: 共有するリソースの管理には std::shared_ptr を用いる †std::shared_ptr を介して使用するオブジェクトのライフタイムは、共同所有権 (shared ownership) を備えたポインタにより管理される。 そのオブジェクトを指す、最後の std::shared_ptr がオブジェクトを指さなくなると、そのstd::shared_ptr がオブジェクトを破棄する。 std::shared_ptr はリソースの reference count からそのリソースを指す最後の std::shared_ptr かどうかを判断できる。 reference count により次の性質を持つ。
std::unique_ptr と同様に std::shared_ptr でもリソースを破棄するデフォルトの方法は delete である。 custom deleter も使用できるが、対応は std::unique_ptr と異なり、 std::unique_ptr では deleter の型がスマートポインタの型の一部になるのに対し、std::shared_ptr ではそうならない。 auto loggingDel = [](Widget *pw) { // custom deleter makeLogEntry(pw); delete pw; }; std::unique_ptr<Widget, decltype(loggingDel)> // deleter type is upw(new Widget, loggingDel); // part of ptr type std::shared_ptr<Widget> // deleter type is not spw(new Widget, loggingDel); // part of ptr tpye std::shared_ptr と std::unique_ptr の違いについて
|