1. ホーム
  2. c++

[解決済み] std::shared_ptr<void>はなぜ動くのか?

2022-06-19 17:56:10

疑問点

シャットダウン時に任意のクリーンアップを行うために std::shared_ptr を使用するいくつかのコードを見つけました。最初はこのコードがうまくいくはずがないと思ったのですが、次のように試してみました。

#include <memory>
#include <iostream>
#include <vector>

class test {
public:
  test() {
    std::cout << "Test created" << std::endl;
  }
  ~test() {
    std::cout << "Test destroyed" << std::endl;
  }
};

int main() {
  std::cout << "At begin of main.\ncreating std::vector<std::shared_ptr<void>>" 
            << std::endl;
  std::vector<std::shared_ptr<void>> v;
  {
    std::cout << "Creating test" << std::endl;
    v.push_back( std::shared_ptr<test>( new test() ) );
    std::cout << "Leaving scope" << std::endl;
  }
  std::cout << "Leaving main" << std::endl;
  return 0;
}

このプログラムは、出力を行います。

At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed

G++で実装されているstd::shared_ptrsの内部と関係があるようです。これらのオブジェクトは内部ポインタをカウンタと一緒にラップしているので std::shared_ptr<test> から std::shared_ptr<void> は、おそらくデストラクタの呼び出しに支障をきたすことはないでしょう。この仮定は正しいでしょうか?

そしてもちろん、より重要な質問です。これは標準によって動作することが保証されていますか、または std::shared_ptr の内部へのさらなる変更、他の実装は実際にこのコードを壊すかもしれませんか?

どのように解決するのですか?

トリックは std::shared_ptr が型消去を行うことです。基本的には、新しい shared_ptr が作成されると、内部で deleter 関数 (これはコンストラクタの引数として与えることができますが、 もし与えられない場合はデフォルトで delete ). このとき shared_ptr が破壊されると、そのストアドファンクションが呼び出され、それが deleter .

std::functionで単純化され、すべての参照カウントや他の問題を回避して行われている型消去の簡単なスケッチは、ここで見ることができます。

template <typename T>
void delete_deleter( void * p ) {
   delete static_cast<T*>(p);
}

template <typename T>
class my_unique_ptr {
  std::function< void (void*) > deleter;
  T * p;
  template <typename U>
  my_unique_ptr( U * p, std::function< void(void*) > deleter = &delete_deleter<U> ) 
     : p(p), deleter(deleter) 
  {}
  ~my_unique_ptr() {
     deleter( p );   
  }
};

int main() {
   my_unique_ptr<void> p( new double ); // deleter == &delete_deleter<double>
}
// ~my_unique_ptr calls delete_deleter<double>(p)

を指定すると shared_ptr が別のものからコピーされる (あるいはデフォルトで構築される) と、デレタが回されます。 shared_ptr<T> から shared_ptr<U> を呼び出した場合、どのデストラクタを呼び出すかという情報も deleter .