Help us understand the problem. What is going on with this article?

子オブジェクトの shared_ptr で親オブジェクトを保持する

c++ オタク向けの話です。
is-A 関係の shared_ptr (継承子クラスの話) は出てくるのに has-A が出てこないのでメモ。

  • std::shared_ptr<C> cptr がある。
  • C クラスにはメンバ型 M の ポインタをくれるような関数がある。
  • こいつを使って std::shared_ptr<M> mptr を受ける関数を呼びたい。
  • mptr がいつ消えるかはわからないので、mptr が消えるまで cptr をキープしたい。

まずは書いて見るわけです。

#include <memory>
#include <iostream>

using M = int;

class C {
 public:
  C(M m) : m_(m) { std::cout << "Hey I'm C, created " << m_ << std::endl; }
  ~C() { std::cout << "Hey I'm C, deleted " << m_ << std::endl; }

  M *GetMPointer() { return &m_; }
 private:
  M m_;
};

int main() {
  std::shared_ptr<M> mptr;
  {
    std::shared_ptr<C> cptr = std::make_shared<C>(3);
    mptr = std::shared_ptr<M>(cptr->GetMPointer());  // Wrong!
  } // ここで cptr が消滅
  std::cout << "M value is " << *mptr << std::endl;
  return 0;
}

実行すると死ぬ。というか環境によっては死なないかもしれないが use-after-free のあと double delete してる。
わからなければ -fsanitize=address -O0 -g つけてコンパイルしてみるとよい。
cptr が消滅するところで C のデストラクタが呼ばれて、そのあと mptr はありえない場所を指している。
明らかに // Wrong! の行がまずい。own してないポインタから shared_ptr を作ってはいけません。

ついついここで

mptr = std::make_shared<M>(*cptr->GetMPointer());  // Not wrong, but..

とかやってしまいがちだが、これは M のコピーを作っている。
コピーを作っていいんなら別にいいし、そのほうが絶対いいと思いますが、コピーを作りたくないこともあるわけです。

delete の部分を置き換えたければcustom deleter を使うという手があります。
cptr が必ず mptr を outlive することが保証されているなら empty deleter を渡せばいいわけです。
lambda が使えるようになって empty deleter が簡単になりました。

mptr = std::shared_ptr<M>(cptr->GetMPointer(), [](M *){}); 

これで、mptr が削除されるときに delete が呼ばれなくなります。
ですが、ただ double delete されないというだけで、上の例で削除後のポインタにアクセスしていることはかわりません (cptr より mptr が長生きしてるから)。こういうことをしているといつか変なメモリエラーを起こしてデバッグで一日消えます。

問題はいつまで mptr が生きているかわからないので、それまで cptr を生かしておきたいわけです。

更新 10/23

コメントで正しい指摘が来たので... shared_ptr のコンストラクタでそのものずばりの機能がありました。
mptr = std::shared_ptr(cptr, cptr->GetMPointer());
とすれば、所有権を共有する shared_ptr が作れます。検索してもなかなか出てこない (検索キーワードがないんですよ) ので気づきませんでした。
@yuki12 さんありがとうございました。解決。

オリジナル

まあもとの文章も間違っているわけではない (まわりくどいだけ) ので、以下に残しておきます。


実はこれも custom deleter + lambda で解決するのでした。

mptr = std::shared_ptr<M>(cptr->GetMPointer(), [cptr](M *){}); 

なんでこれが動くのか一見よくわかりません。渡しているのは、キャプチャがある空の lambda 関数です。
種明かしすると、deleter も仕事が終わったらデリートされるわけです。この deleter は cptr を値渡しでキャプチャしてるので、deleter の中で shared_ptr の変数が一個作られて、cptr のコピーを保持しています。関数本体では使ってないんですが、この deleter が終わったときに cptr のコピーが削除されてくれるわけです。
問題の行を最後の例に変えて実行すると、ほらちゃんと M の値を使ってから C が delete されているのが見えます。

Hey I'm C, created 3
M value is 3
Hey I'm C, deleted 3

以上。

ta_makino
Twitter @ta_makino
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした