1. ホーム
  2. c++

[解決済み] スマートポインタを常に使用するのは良い習慣ですか?

2023-06-08 04:52:42

質問

私はスマートポインタは生ポインタよりもずっと快適だと感じています。そこで、以下のようにするのは良いアイデアでしょうか? 常に スマートポインタを使うのが良いのでしょうか?(私はJava出身なので、明示的なメモリ管理の考え方はあまり好きではないことに注意してください。ですから、スマートポインタの性能に深刻な問題がない限り、スマートポインタを使い続けたいと思います。)

注:私はJava出身ですが、スマートポインタの実装とRAIIの概念についてはよく理解しています。ですから、回答を投稿するときは、私の側からこの知識を当然と考えることができます。私はほとんどすべての場所で静的割り当てを使用し、必要な場合にのみポインタを使用します。私の質問は単に 私は常に生のポインタの代わりにスマートポインタを使用することができますか?

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

いくつかの編集を考えると、包括的な要約が有用であるような印象を受けます。

1. しない場合

スマートポインタを使うべきではない状況が2つあります。

1つ目は、スマートポインタを使うべきではない状況と全く同じです。 C++ クラスを実際に使用してはいけません。IE:ソースコードをクライアントに提供しない場合、DLL境界。逸話的に言ってみましょう。

2つ目はもっと頻繁に起こります。 スマート・マネージャーはオーナーシップを意味する . 例えば、既存のリソースの寿命を管理せずに、ポインタを使用して指し示すことがあります。

void notowner(const std::string& name)
{
  Class* pointer(0);
  if (name == "cat")
    pointer = getCat();
  else if (name == "dog")
    pointer = getDog();

  if (pointer) doSomething(*pointer);
}

この例では制約があります。しかし、ポインタは無効な場所(ヌルポインタ)を指すことがあるという点で、参照とは意味的に異なるものです。この場合、オブジェクトのライフタイムを管理したくないので、代わりにスマートポインタを使わなくても全く問題ありません。

2. スマートマネージャ

スマート・マネージャ・クラスを記述していない限り、もしキーワード delete を使用すると、何か間違ったことをしていることになります。

これは賛否両論のある視点ですが、多くの欠陥のあるコードの例をレビューした後、私はもうこれ以上チャンスを逃さないようにしています。ですから、もしあなたが new と書くなら、新しく割り当てられたメモリのためにスマートなマネージャが必要です。そして、今すぐそれが必要なのです。

だからといって、プログラマーとしての資質が低いというわけではありません! それどころか、車輪を何度も再発明する代わりに、動作が証明されているコードを再利用することは重要なスキルです。

さて、本当に難しいのは、どのようなスマートなマネージャーが必要なのか、ということです。

3. スマートポインタ

世の中には様々なスマートポインタがあり、様々な特徴があります。

スキップ std::auto_ptr は一般的に避けるべきものです(そのコピーセマンティックはねじ曲げられます)。

  • scoped_ptr : オーバーヘッドがなく、コピーや移動ができません。
  • unique_ptr : オーバーヘッドなし、コピー不可、移動可。
  • shared_ptr / weak_ptr : いくつかのオーバーヘッド (参照カウント) がありますが、コピーすることができます。

通常、以下のどちらかを使用するようにしてください。 scoped_ptr または unique_ptr . 複数のオーナーが必要な場合は、デザインを変更してみてください。もしデザインを変更することができず、本当に複数のオーナーが必要な場合は shared_ptr を使うことができますが、を使うと壊れるはずの参照サイクルが壊れるので注意が必要です。 weak_ptr を使うべきです。

4. スマートコンテナ

多くのスマートポインタはコピーされることを意図していないため、STLコンテナでの使用は多少危ういです。

に頼るのではなく shared_ptr とそのオーバーヘッドに頼らず、スマートコンテナで ブーストポインタコンテナ . これらは古典的なSTLコンテナのインタフェースをエミュレートしていますが、自分自身のポインタを格納します。

5. 自分でロールする

独自のスマートマネージャをロールバックしたい場合があります。その際、使用しているライブラリの機能を見逃していないか、事前に確認してください。

例外が発生する状況でスマートマネージャを書くのは非常に難しいです。通常、メモリが利用可能であることを仮定することはできません ( new が失敗する可能性がある)、あるいは Copy Constructor があります。 no throw を保証します。

を無視しても、多少は許容されるかもしれません。 std::bad_alloc 例外を無視して Copy Constructor が失敗しないようにする必要があります...結局のところ、それは boost::shared_ptr はそのデレッターのために D テンプレート・パラメータに適用されます。

しかし、特に初心者の方にはお勧めしません。厄介な問題で、今すぐにはバグに気づけないでしょうから。

6. 事例紹介

// For the sake of short code, avoid in real code ;)
using namespace boost;

// Example classes
//   Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
//   It is to obey the `Cloneable` concept as described in 
// the Boost Pointer Container library linked above
struct Cloneable
{
  virtual ~Cloneable() {}
  virtual Cloneable* clone() const = 0;
};

struct Derived: Cloneable
{
  virtual Derived* clone() const { new Derived(*this); }
};

void scoped()
{
  scoped_ptr<Cloneable> c(new Derived);
} // memory freed here

// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
  return unique_ptr<Cloneable>(new Derived);
}

void shared()
{
  shared_ptr<Cloneable> n1(new Derived);
  weak_ptr<Cloneable> w = n1;

  {
    shared_ptr<Cloneable> n2 = n1;          // copy

    n1.reset();

    assert(n1.get() == 0);
    assert(n2.get() != 0);
    assert(!w.expired() && w.get() != 0);
  } // n2 goes out of scope, the memory is released

  assert(w.expired()); // no object any longer
}

void container()
{
  ptr_vector<Cloneable> vec;
  vec.push_back(new Derived);
  vec.push_back(new Derived);

  vec.push_back(
    vec.front().clone()         // Interesting semantic, it is dereferenced!
  );
} // when vec goes out of scope, it clears up everything ;)