10

私は、 C ++ FAQで基本クラスの興味深い実装に出くわしました。これは、私の素朴な理解によれば、いくつかのスマートポインター実装(例:shared_ptr)の代替として役立つ可能性があります。これが逐語的なサンプルコードですが、説明については上記のリンクをたどってください:

class Fred {
public:

  static Fred create1(std::string const& s, int i);
  static Fred create2(float x, float y);

  Fred(Fred const& f);
  Fred& operator= (Fred const& f);
 ~Fred();

  void sampleInspectorMethod() const;   // No changes to this object
  void sampleMutatorMethod();           // Change this object

  ...

private:

  class Data {
  public:
    Data() : count_(1) { }
    Data(Data const& d) : count_(1) { }              // Do NOT copy the 'count_' member!
    Data& operator= (Data const&) { return *this; }  // Do NOT copy the 'count_' member!
    virtual ~Data() { assert(count_ == 0); }         // A virtual destructor
    virtual Data* clone() const = 0;                 // A virtual constructor
    virtual void sampleInspectorMethod() const = 0;  // A pure virtual function
    virtual void sampleMutatorMethod() = 0;
  private:
    unsigned count_;   // count_ doesn't need to be protected
    friend class Fred; // Allow Fred to access count_
  };

  class Der1 : public Data {
  public:
    Der1(std::string const& s, int i);
    virtual void sampleInspectorMethod() const;
    virtual void sampleMutatorMethod();
    virtual Data* clone() const;
    ...
  };

  class Der2 : public Data {
  public:
    Der2(float x, float y);
    virtual void sampleInspectorMethod() const;
    virtual void sampleMutatorMethod();
    virtual Data* clone() const;
    ...
  };

  Fred(Data* data);
  // Creates a Fred smart-reference that owns *data
  // It is private to force users to use a createXXX() method
  // Requirement: data must not be NULL

  Data* data_;   // Invariant: data_ is never NULL
};

Fred::Fred(Data* data) : data_(data)  { assert(data != NULL); }

Fred Fred::create1(std::string const& s, int i) { return Fred(new Der1(s, i)); }
Fred Fred::create2(float x, float y)            { return Fred(new Der2(x, y)); }

Fred::Data* Fred::Der1::clone() const { return new Der1(*this); }
Fred::Data* Fred::Der2::clone() const { return new Der2(*this); }

Fred::Fred(Fred const& f)
  : data_(f.data_)
{
  ++data_->count_;
}

Fred& Fred::operator= (Fred const& f)
{
  // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
  // (This order properly handles self-assignment)
  // (This order also properly handles recursion, e.g., if a Fred::Data contains Freds)
  Data* const old = data_;
  data_ = f.data_;
  ++data_->count_;
  if (--old->count_ == 0) delete old;
  return *this;
}

Fred::~Fred()
{
  if (--data_->count_ == 0) delete data_;
}

void Fred::sampleInspectorMethod() const
{
  // This method promises ("const") not to change anything in *data_
  // Therefore we simply "pass the method through" to *data_:
  data_->sampleInspectorMethod();
}

void Fred::sampleMutatorMethod()
{
  // This method might need to change things in *data_
  // Thus it first checks if this is the only pointer to *data_
  if (data_->count_ > 1) {
    Data* d = data_->clone();   // The Virtual Constructor Idiom
    --data_->count_;
    data_ = d;
  }
  assert(data_->count_ == 1);

  // Now we "pass the method through" to *data_:
  data_->sampleMutatorMethod();
}

このアプローチがC++ライブラリで使用されていることはわかりません。かなりエレガントに見えますが。シングルスレッド環境を想定すると、簡単にするために、次の質問に答えてください。

  1. これは、オブジェクトの存続期間を管理するためのスマートポインターアプローチの適切な代替手段ですか、それとも単に問題を求めているだけですか?
  2. それが適切であるならば、なぜあなたはそれがより頻繁に使われないと思いますか?
4

4 に答える 4

8

これは、オブジェクトの存続期間を管理するためのスマートポインターアプローチの適切な代替手段ですか、それとも単に問題を求めているだけですか?

いいえ、特にC++11でstd::shared_ptrを使用しているため、参照カウントを再発明することはお勧めできません。std :: shared_ptrの観点から、ポリモーフィックな参照カウントされたPimplイディオムクラスを簡単に実装できます。copy ctor、assignment、dtorを実装する必要がなくなり、参照カウンターとクローン作成によりミューテーションが簡単になることに注目してください。

// to be placed into a header file ...

#include <memory>
#include <utility>
#include <string>

class Fred
{
public:
    static Fred create1(std::string const& s, int i);
    static Fred create2(float x, float y);

    void sampleInspectorMethod() const;   // No changes to this object
    void sampleMutatorMethod();           // Change this object

private:
    class Data;
    std::shared_ptr<Data> data_;

    explicit Fred(std::shared_ptr<Data> d) : data_(std::move(d)) {}
};

...そして実装...

// to be placed in the corresponding CPP file ...

#include <cassert>
#include "Fred.hpp"

using std::shared_ptr;

class Fred::Data
{
public:
    virtual ~Data() {}                               // A virtual destructor
    virtual shared_ptr<Data> clone() const = 0;      // A virtual constructor
    virtual void sampleInspectorMethod() const = 0;  // A pure virtual function
    virtual void sampleMutatorMethod() = 0;
};

namespace {

class Der1 : public Fred::Data
{
public:
    Der1(std::string const& s, int i);
    virtual void sampleInspectorMethod() const;
    virtual void sampleMutatorMethod();
    virtual shared_ptr<Data> clone() const;
    ...
};

// insert Der1 function definitions here

class Der2 : public Data
{
public:
    Der2(float x, float y);
    virtual void sampleInspectorMethod() const;
    virtual void sampleMutatorMethod();
    virtual shared_ptr<Data> clone() const;
    ...
};

// insert Der2 function definitions here

} // unnamed namespace

Fred Fred::create1(std::string const& s, int i)
{
    return Fred(std::make_shared<Der1>(s,i));
}

Fred Fred::create2(float x, float y)
{
    return Fred(std::make_shared<Der2>(x,y));
}

void Fred::sampleInspectorMethod() const
{
    // This method promises ("const") not to change anything in *data_
    // Therefore we simply "pass the method through" to *data_:
    data_->sampleInspectorMethod();
}

void Fred::sampleMutatorMethod()
{
    // This method might need to change things in *data_
    // Thus it first checks if this is the only pointer to *data_
    if (!data_.unique()) data_ = data_->clone();
    assert(data_.unique());

    // Now we "pass the method through" to *data_:
    data_->sampleMutatorMethod();
}

(未テスト)

それが適切であるならば、なぜあなたはそれがより頻繁に使われないと思いますか?

参照カウントは、自分で実装すると、間違えやすくなると思います。また、参照カウンターをアトミックにインクリメントおよびデクリメントする必要があるため、マルチスレッド環境では低速であるという評判があります。しかし、shared_ptrとmoveセマンティクスを提供するC ++ 11のおかげで、このコピーオンライトパターンはもう少し人気が出るかもしれません。Fredクラスの移動セマンティクスを有効にすると、参照カウンターをアトミックにインクリメントするコストの一部を回避できます。したがって、Fredオブジェクトをある場所から別の場所に移動することは、それをコピーするよりもさらに高速であるはずです。

于 2012-08-06T07:44:57.707 に答える
4

これは、オブジェクトの有効期間を管理するためのスマート ポインター アプローチに代わる適切な方法ですか?

これは代替手段ですが、それを使用する非常に正当な理由がない限り、(再利用できない方法で) 車輪を再発明するだけです。

代わりに shared_ptr を使用するようにコードを変更すると、コピー/所有権のセマンティクスを明示的に定義する (および pimpl ベースでコピー コンストラクターと代入を定義する) 必要がなくなります。また、既に定義およびテストされているコードも使用します (ライブラリの一部であるため)。

適切な場合、なぜあまり使われていないと思いますか?

shared_ptr が利用可能であり、機能とすべての「落とし穴」を既に実装しているためです。

于 2012-08-07T13:05:44.370 に答える
3

私も、スマートポインターの代替として適しているかなと思います。

しかし、IMO、スマートポインターになるには、クラスをポインターとして使用できる必要があります。

SmartPtr<int> ptr = new int(42);
int x = *ptr;

はい、それは一種のメモリ管理ですが、ポインターのセマンティックを持たないため、スマートポインターではありません。

コメントで述べたように、pimpl イディオムは互換性を維持するのに非常に役立ち、含まれているクラスを再コンパイルする必要がないため、開発を促進することもできます。ただし、後者の利点を得るには、親クラスの内部で内部クラス (つまり、データ) を定義するのではなく、単に前方宣言を行い、実際の定義を別のヘッダー内に配置する必要があります。

class Fred {
    ...
private:

class Data;

};

また、Fred クラス内で Data のバリアントを宣言することは、今後の開発では役に立たないと思います。別のクラスを追加する必要がある場合は、単に別のクラスを作成するのではなく、Fred を変更する必要があるからです。これは必要かもしれませんが、その部分は避けることをお勧めします。

わからないことがあれば、遠慮なく質問してください!

于 2012-08-03T02:49:10.010 に答える
3
  1. C++ FAQ の回答は、共有データを管理する方法 (コピー オン ライトを使用) の単純な例のようです。欠けている側面がいくつかありますが、それは重要かもしれません。

  2. 1に対する私の意見では該当なし。

Andrei Alexandrescu の著書Modern C++ Designで説明されているように、「外部」参照カウントで導入されるオーバーヘッドを回避するにstd::shared_ptrは、侵入型参照カウント メカニズムを使用できます。Loki::COMRefCountedクラスは、Windows 共有 COM オブジェクトに対してそのような所有権ポリシーを実装する方法を示しています。

基本的には、スマート ポインター テンプレート クラスが、参照カウントと参照delete先クラス インスタンス自体での可用性のチェックを管理するインターフェイスを受け入れることになります。std::shared_ptrSTD C++ ライブラリがそのようなクラスのポリシー オーバーライドの実装をサポートしているかどうかはわかりません。

多くの組み込みプロジェクトで、 Loki ライブラリをスマート ポインター モデル専用に使用しており、非常に成功しています。特に、この機能により、効率の細かい粒度の側面をモデル化できます。

提案された (組み込みの) 実装は、デフォルトではスレッドセーフではないことに注意してください。

上記のすべての側面が目的に関係しない場合は、selibitze の回答に示されているように、クラスを簡単std::shared_ptrに表現することをお勧めします。Fred::Dataまた、彼が最後の段落で述べた点にも同意します。参照カウントとスマート ポインターのセマンティクスは、誤解され、間違って実装される傾向があります。

C++11 標準またはブーストが適していない場合でも、loki ライブラリは統合が容易で堅牢なスマート ポインターの実装を提供します。

于 2012-08-06T18:44:40.837 に答える