web-dev-qa-db-ja.com

scoped_ptrをメンバー変数として使用するC ++

デザインの質問について意見が欲しかった。他のオブジェクトを所有するよりもC++クラスがある場合、これを実現するためにスマートポインターを使用しますか?

class Example {
public: 
  // ...

private:
  boost::scoped_ptr<Owned> data;
};

'Owned'オブジェクトは、オブジェクトの存続期間を通じて変更される可能性があるため、値で保存することはできません。

私の見解では、一方ではオブジェクトが所有されていることを明確にし、その削除を確実にしますが、反対に、通常のポインターを持ってデストラクタで簡単に削除することができます。これはやり過ぎですか?

フォローアップ:すべての回答に感謝します。オブジェクト全体がコピーされるときにauto_ptrが他のオブジェクトにNULLポインターを残すことについての注意を払ってくれてありがとう、私はauto_ptrを広範囲に使用しましたが、まだそれについて考えていませんでした。私は基本的にすべてのクラスをboost :: noncopyableにしますが、正当な理由がない限り、心配する必要はありません。また、例外でのメモリリークに関する情報にも感謝します。これも知っておくとよいでしょう。とにかくコンストラクターで例外を引き起こす可能性のあるものを書かないようにしています-それを行うためのより良い方法があります-それは問題ではないはずです。

しかし、別の質問がありました。この質問をしたときに私が欲しかったのは、誰かが実際にこれを行ったかどうかを知ることでした。理論的には良い考えだと皆さんは言っているようですが、実際に行っているとは誰も言っていません。びっくり!確かに、別のオブジェクトへのポインタを所有している1つのオブジェクトは新しいアイデアではありません。ある時点で、皆さんが以前にそれを行ったことがあると思います。どうしたの?

33
Ray Hidayat

いい考えだね。これにより、コードが簡素化され、オブジェクトの存続期間中に所有オブジェクトを変更したときに、前のオブジェクトが適切に破棄されるようになります。

ただし、scoped_ptrはコピーできないことを覚えておく必要があります。これにより、独自のコピーコンストラクターなどを追加するまで、または追加しない限り、デフォルトでクラスはコピーできなくなります(もちろん、生のポインターの場合にデフォルトのコピーコンストラクターを使用することはできません。いいえ!)

クラスに複数のポインタフィールドがある場合、scoped_ptrを使用すると、実際には次の1つのケースで例外安全性が向上します。

class C
{
  Owned * o1;
  Owned * o2;

public:
  C() : o1(new Owned), o2(new Owned) {}
  ~C() { delete o1; delete o2;}
};

ここで、Cの構築中に、2番目の「新しい所有」が例外(たとえば、メモリ不足)をスローすると想像してください。オブジェクトがまだ完全に構築されていないため、C :: 〜C()(デストラクタ)が呼び出されないため、o1がリークされます。ただし、完全に構築されたメンバーフィールドのデストラクタdoesが呼び出されます。したがって、プレーンポインタの代わりにscoped_ptrを使用すると、o1を適切に破棄できます。

29
SCFrench

scoped_ptrは、この目的に非常に適しています。しかし、そのセマンティクスを理解する必要があります。 2つの主要なプロパティを使用してスマートポインタをグループ化できます。

  • コピー可能:スマートポインターをコピーできます:コピーと元の共有の所有権。
  • 移動可能:スマートポインターを移動できます:移動結果には所有権があり、元の結果には所有権がありません。

これはかなり一般的な用語です。スマートポインタの場合、これらのプロパティをより適切にマークする特定の用語があります。

  • 所有権の譲渡:スマートポインターは移動可能です
  • 所有権の共有:スマートポインターはコピー可能です。スマートポインタがすでにコピー可能である場合、所有権の譲渡セマンティクスをサポートするのは簡単です。それは単なるアトミックcopy&reset-of-original操作であり、特定の種類のスマートポインタに制限されます(例:一時的なスマートポインタのみ)。

(C)opyable、および(M)ovable(N)eitherを使用して、使用可能なスマートポインターをグループ化しましょう。

  1. boost::scoped_ptr:N
  2. std::auto_ptr:M
  3. boost::shared_ptr:C

auto_ptrには、コピーコンストラクターを使用してMovableの概念を実現するという大きな問題が1つあります。これは、auto_ptrがC++に受け入れられたとき、新しいC++標準とは対照的に、移動コンストラクターを使用して移動セマンティクスをネイティブにサポートする方法がまだなかったためです。つまり、auto_ptrを使用して次のことを実行でき、それが機能します。

auto_ptr<int> a(new int), b;
// oops, after this, a is reset. But a copy was desired!
// it does the copy&reset-of-original, but it's not restricted to only temporary
// auto_ptrs (so, not to ones that are returned from functions, for example).
b = a; 

とにかく、私たちが見るように、あなたの場合、所有権を別のオブジェクトに譲渡することはできません。あなたのオブジェクトは事実上コピーできません。そして、次のC++標準では、scoped_ptrを使用したままにすると、移動できなくなります。

Scoped_ptrを使用してクラスを実装するには、次の2つのポイントのいずれかが満たされていることに注意してください。

  • クラスの.cppファイルにデストラクタ(空の場合でも)を書き込むか、または
  • Ownedを完全に定義するクラスにします。

それ以外の場合、Exampleのオブジェクトを作成すると、コンパイラは暗黙的にデストラクタを定義し、scoped_ptrのデストラクタを呼び出します。

~Example() { ptr.~scoped_ptr<Owned>(); }

次に、scoped_ptrがboost::checked_deleteを呼び出します。これは、上記の2つのポイントのいずれも実行していない場合、Ownedが不完全であると文句を言います。 .cppファイルで独自のdtorを定義した場合、scoped_ptrのデストラクタへの暗黙の呼び出しは.cppファイルから行われ、そこにOwnedクラスの定義を配置できます。

Auto_ptrでも同じ問題がありますが、もう1つ問題があります。auto_ptrに不完全な型を提供することは、現在未定義の動作です(おそらく、次のC++バージョンで修正される予定です)。したがって、auto_ptrを使用する場合は、haveを使用して、ヘッダーファイル内でOwnedを完全な型にします。

shared_ptrは、deleteを間接的に呼び出す多形のdeleterを使用するため、この問題は発生しません。したがって、削除関数は、デストラクタがインスタンス化された時点ではインスタンス化されませんが、shared_ptrのコンストラクタで削除機能が作成された時点でインスタンス化されます。

それは決してやり過ぎではありません、それは良い考えです。

ただし、クラスのクライアントはブーストについて知っている必要があります。これは問題になる場合とそうでない場合があります。移植性のために、(この場合)同じ仕事をするstd :: auto_ptrを検討することができます。プライベートなので、他の人がコピーしようとすることを心配する必要はありません。

8
CB Bailey

Scoped_ptrを使用することをお勧めします。

ポインタを保持して手動で破棄することは、思ったほど簡単ではありません。特に、コードに複数のRAWポインタがある場合。例外安全性とメモリリークを優先しないことが優先される場合は、それを正しくするために多くの追加コードが必要です。

まず、4つのデフォルトメソッドすべてを正しく定義していることを確認する必要があります。これは、これらのメソッドのコンパイラ生成バージョンは通常のオブジェクト(スマートポインタを含む)には問題ないが、通常の場合、ポインタの処理に問題が発生するためです(浅いコピーの問題を探してください)。

  • デフォルトコンストラクタ
  • コピーコンストラクタ
  • 代入演算子
  • デストラクタ

Scoped_ptrを使用する場合は、それらについて心配する必要はありません。

これで、クラスに複数のRAWポインターがある場合(またはコンストラクターの他の部分がスローできる場合)。建設中および破壊中の例外に明示的に対処する必要があります。

class MyClass
{
    public:
        MyClass();
        MyClass(MyClass const& copy);
        MyClass& operator=(MyClass const& copy);
        ~MyClass();

    private
        Data*    d1;
        Data*    d2;
};

MyClass::MyClass()
    :d1(NULL),d2(NULL)
{
    // This is the most trivial case I can think off
    // But even it looks ugly. Remember the destructor is NOT called
    // unless the constructor completes (without exceptions) but if an
    // exception is thrown then all fully constructed object will be
    // destroyed via there destructor. But pointers don't have destructors.
    try
    {
        d1 = new Data;
        d2 = new Data;
    }
    catch(...)
    {
        delete d1;
        delete d2;
        throw;
    }
}

Scopted_ptrがどれほど簡単か見てください。

5
Martin York

スコープ付きポインターは、プログラマーとして心配することなくオブジェクトを確実に削除できるため、まさにこれに適しています。これはスコープ付きptrの良い使い方だと思います。

一般に、優れた設計戦略は、メモリを手動で解放することをできるだけ避け、ツール(この場合はスマートポインタ)に自動的に解放させることです。私が見ることができるように、手動削除は1つの主な理由で悪いです、そしてそれはコードが非常に迅速に維持するのが難しくなるということです。多くの場合、メモリの割り当てと割り当て解除のロジックはコード内で分離されているため、補完的な行が一緒に維持されません。

4
Daniel Nadasi

これはやり過ぎではないと思います。これは、生のポインターを持つよりもはるかに優れたメンバーのセマンティクスを文書化し、エラーが発生しにくいです。

4
Motti

なぜやり過ぎ? boost :: scoped_ptrは非常に簡単に最適化でき、結果のマシンコードは、デストラクタでポインタを手動で削除した場合と同じになると思います。

scoped_ptrは良いです-それを使用してください:)

3