#author("2016-11-16T07:16:16+00:00","default:nitta","nitta") #author("2016-11-16T07:50:26+00:00","default:nitta","nitta") [[C++/C++14]] Effective Modern C++ - C++11/14 プログラムを進化させる42項目 Scott Meyers O'reilly Japan ISBN978-4-87311-736-2 *[[C++/C++14/SmartPointer]] [#i29cd8c9] C++11のスマートポインタ - std::auto_ptr C++98から残されたもので非推奨。 - std::unique_ptr std::auto_ptr でできることはすべて可能で、それ以上のものに効率的に対処できる。 - std::shared_ptr - std::weak_ptr **項目18: 独占するリソースの管理には std::unique_ptr を用いる [#j0e0a0e0] *項目18: 独占するリソースの管理には std::unique_ptr を用いる [#j0e0a0e0] 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; } - makeInvestment が返したオブジェクトの custom deleter は delInvmt である。 - custom deleter を使用する場合は、その型を std::unique_ptr の第2引数へ指定する。 - raw ポインタを std::unique_ptr へ代入しようとしてもコンパイルできない。そのため、new により作成したオブジェクトの所有権を pInv に持たせるよう reset を用いている。 - new を実行するたびに std::forward におり makeInvestment に渡された実引数を完全転送している。このため、呼び出し側から与えられた情報をオブジェクトのコンストラクタがすべて使用できる。 - custom deleter の仮引数の型は Investment* である。makeInvestment 内で作成したオブジェクトの実際の型にかかわらず、最後には Investment* オブジェクトとしてラムダ式内の delete により破棄される。すなわち、基底クラスのポインタを用い派生クラスオブジェクトを破棄する。これを実現するには基底クラス Investment が仮想デストラクタを実装する必要がある。 class Investment { public: ... virtual ~Investment(); ... } 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つの形式がある。 - 単一オブジェクト用 std::unique_ptr - 配列 std::unique_ptr<T[]> ***重要ポイント [#uaae81a0] **重要ポイント [#uaae81a0] - std::unique_ptr は、独占所有するリソース管理用の、サイズが小さく、高速な、ムーブ専用のスマートポインタである。 - デフォルトでは、リソースは delete により破棄されるが、custom deleter も指定可能である。状態を持つ deleter や、関数ポインタの deleter は std::unique_ptr オブジェクトのサイズを増加させる。 - std::unique_ptr の std::shared_ptr への変換は容易である。 *項目19: 共有するリソースの管理には std::shared_ptr を用いる [#ibab92f2] std::shared_ptr を介して使用するオブジェクトのライフタイムは、共同所有権 (shared ownership) を備えたポインタにより管理される。 そのオブジェクトを指す、最後の std::shared_ptr がオブジェクトを指さなくなると、そのstd::shared_ptr がオブジェクトを破棄する。 std::shared_ptr はリソースの reference count からそのリソースを指す最後の std::shared_ptr かどうかを判断できる。 reference count により次の性質を持つ。 - shared::shared_ptr のサイズが raw ポインタの2倍になる。 - reference counter を動的にメモリ割り当てしなければならない。 - reference counter の increment/decrement はアトミックに実行しなければならない。 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 の違いについて - std::shared_ptr の設計の方が柔軟性に優れている。 ~ std::shared_ptr<widget> が2つあり、それぞれ異なる型の custom deleter を持つとする。 auto customDeleter1 = [](Widget *pw) { ... }; // custom deleters, each auto customDeleter2 = [](Widget *pw) { ... }; // with a different type std::shared_ptr<Widget> pw1(new Widget, customDeleter1); std::shared_ptr<Widget> pw1(new Widget, customDeleter2); 上の例の pw1 と pw2 の型は同じため、同じ型のコンテナに持たせられる。 std::vector<std::shared_ptr<Widget>> vpw[ pw1, pw2 }; また、pw1 と pw2 は相互に代入可能であり、いずれも std::shared_ptr<Widget> 型の仮引数をとる関数へ渡せる。 - custom deleter をしていしても std::shared_ptr のサイズには影響しない。~ std::shared_ptr オブジェクトのサイズは deleter に関係なくポインタ2つ分である。