34

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

class Example {
public: 
  // ...

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

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

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

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

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

4

7 に答える 7

40

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

  • コピー可能: スマート ポインターをコピーできます: コピーと元の共有所有権。
  • 移動可能: スマート ポインターを移動できます。移動結果は所有権を持ち、元のポインターは所有しなくなります。

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

  • 所有権の譲渡: スマート ポインターは可動です
  • 所有権の共有: スマート ポインターはコピー可能です。スマート ポインターが既にコピー可能である場合、所有権の譲渡セマンティックをサポートするのは簡単です。これは、特定の種類のスマート ポインター (たとえば、一時的なスマート ポインターのみ) に制限する、単なるアトミックコピーおよび元のリセット操作です。

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

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

auto_ptrには、コピー コンストラクターを使用して Moverable の概念を実現するという点で、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 callが行われ、上記の 2 つの点のいずれも行っていない場合、不完全でboost::checked_deleteあると文句を言います。Owned.cpp ファイルで独自の dtor を定義した場合、scoped_ptr のデストラクタへの暗黙的な呼び出しは、Ownedクラスの定義を配置できる .cpp ファイルから行われます。

auto_ptr にも同じ問題がありますが、もう 1 つ問題があります。auto_ptr に不完全な型を指定すると、現在未定義の動作です (次の C++ バージョンで修正される可能性があります)。したがって、auto_ptr を使用する場合、ヘッダー ファイル内で Owned を完全な型にする必要があります。

shared_ptr には、delete を間接的に呼び出すポリモーフィックな削除機能が使用されているため、この問題はありません。したがって、削除関数は、デストラクタがインスタンス化された時点ではインスタンス化されませんが、shared_ptr のコンストラクタでデリータが作成された時点でインスタンス化されます。

于 2009-02-01T19:09:58.327 に答える
30

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

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

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

class C
{
  Owned * o1;
  Owned * o2;

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

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

于 2009-02-01T12:59:36.763 に答える
8

それはまったくやり過ぎではありません、それは良い考えです。

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

于 2009-02-01T11:52:47.803 に答える
5

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 がどれほど簡単か見てください。

于 2009-02-01T17:25:38.677 に答える
4

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

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

于 2009-02-01T11:53:06.617 に答える
4

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

于 2009-02-01T11:53:06.973 に答える
3

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

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

于 2009-02-01T18:07:37.420 に答える