C++/C++14

Effective Modern C++
 - C++11/14 プログラムを進化させる42項目
Scott Meyers
O'reilly Japan
ISBN978-4-87311-736-2

C++/C++14/SmartPointer

C++11のスマートポインタ

  • std::auto_ptr C++98から残されたもので非推奨。
  • std::unique_ptr std::auto_ptr でできることはすべて可能で、それ以上のものに効率的に対処できる。
  • std::shared_ptr
  • std::weak_ptr

項目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;
}
  • 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[]>

重要ポイント

  • std::unique_ptr は、独占所有するリソース管理用の、サイズが小さく、高速な、ムーブ専用のスマートポインタである。
  • デフォルトでは、リソースは delete により破棄されるが、custom deleter も指定可能である。状態を持つ deleter や、関数ポインタの deleter は std::unique_ptr オブジェクトのサイズを増加させる。
  • std::unique_ptr の std::shared_ptr への変換は容易である。

項目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 により次の性質を持つ。

  • 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つ分である。

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-11-16 (水) 16:50:26 (367d)