9

この洞察に満ちた記事では、Qt プログラマーの 1 人が、Qt が実装するさまざまな種類のスマート ポインターについて説明しようとしています。最初に、彼はデータの共有とポインター自体の共有を区別しています。

まず、1 つはっきりさせておきます。ポインターの共有とデータの共有には違いがあります。ポインターを共有すると、ポインターの値とその有効期間はスマート ポインター クラスによって保護されます。つまり、ポインタは不変です。ただし、ポインターが指しているオブジェクトは、完全に制御できません。オブジェクトがコピー可能かどうか、代入可能かどうかはわかりません。

現在、データの共有には、共有されているデータについて何かを知っているスマート ポインター クラスが含まれます。実際、要点は、データが共有されているということであり、その方法は気にしません。データを共有するためにポインターが使用されているという事実は、この時点では関係ありません。たとえば、Qt ツール クラスがどのように暗黙的に共有されているかはあまり気にしませんか? あなたにとって重要なのは、それらが共有されていること (したがって、メモリ消費を削減すること) と、共有されていないかのように機能することです。

率直に言って、私はこの説明を理解していません。記事のコメントに明確化の嘆願がありましたが、著者の説明が十分であるとは思いませんでした.

このことを理解できるなら、説明してくださいこの違いは何ですか? また、他の共有ポインタ クラス (つまり、boost または新しい C++ 標準から) はどのようにこの分類法に適合しますか?

前もって感謝します

4

3 に答える 3

7

後のコメントで、彼は問題を少し明確にします

これは、私が最初のセクションで理解しようとした重要なポイントです。QSharedPointerを使用すると、ポインターの所有権を共有することになります。クラスはポインタのみを制御および処理します—他のもの(データへのアクセスなど)はそのスコープ外です。QSharedDataPointerを使用すると、データを共有することになります。そして、そのクラスは暗黙の共有を目的としているため、分割される可能性があります。

それを解釈しようとしています:

重要なのは、この場合、「ポインタ」はアドレスを格納するオブジェクトを意味するのではなく、オブジェクトが配置されている格納場所(アドレス自体)を意味するということです。厳密には、アドレスを共有していると言わなければならないと思います。boost::shared_ptrしたがって、は「ポインタ」を共有するスマートポインタです。boost::intrusive_ptrまたは、別の侵入型スマートポインターも、ポインターを共有しているように見えますが、ポイントされているオブジェクトについて何かを知っています(参照カウントメンバーまたはそれをインクリメント/デクリメントする関数があること)。

例:誰かがブラックボックスをあなたと共有し、その人がブラックボックスの内容を知らない場合、それはポインター(ボックスを表す)を共有することに似ていますが、データ(ボックスの内部にあるもの)は共有しません。実際、ボックスの中にあるものが共有可能であることさえ知ることはできません(ボックスに何も含まれていない場合はどうなりますか?)。スマートポインタはあなたと他の人によって表されます(もちろん、あなたは共有されません)が、アドレスはボックスであり、共有されます。

データを共有するということは、スマートポインターがポイントされたデータを十分に認識しているため、ポイントされたアドレスを変更する可能性があることを意味します(これはデータをコピーする必要があるなど)。そのため、ポインタが異なるアドレス指すようになりました。アドレスが異なるため、アドレスは共有されなくなりました。これはstd::string、一部の実装でも同様です。

std::string a("foo"), b(a);
 // a and b may point to the same storage by now.
std::cout << (void*)a.c_str(), (void*)b.c_str();
 // but now, since you could modify data, they will
 // be different
std::cout << (void*)&a[0], (void*)&b[0];

データを共有することは、必ずしもあなたにポインターが提示されることを意味するわけではありません。std::string純粋な手段でをa[0]使用することができ、cout << a;どの機能にも触れないでc_str()ください。それでも共有は舞台裏で行われる可能性があります。同じことが多くのQtクラスや他のウィジェットツールキットのクラスでも起こります。これは暗黙の共有(またはコピーオンライト)と呼ばれます。だから私はそれを次のように要約するかもしれないと思います:

  • ポインターの共有:スマートポインターをコピーするときは常に同じアドレスを指します。これは、ポインター値を共有することを意味します。
  • データの共有:異なる時間に異なるアドレスを指す場合があります。あるアドレスから別のアドレスにデータをコピーする方法を知っていることを意味します。

だから分類しようとしています

  • boost::shared_ptrboost::intrusive_ptr:データではなくポインタを共有します。
  • QString、、 :含まれているデータQPenQSharedDataPointer共有します。
  • std::unique_ptrstd::auto_ptr(およびQScopedPointer):ポインタもデータも共有しません。
于 2010-04-17T13:05:30.323 に答える
3

このクラスがあったとしましょう

struct BigArray{
   int  operator[](size_t i)const{return m_data[i];}
   int& operator[](size_t i){return m_data[i];}
private:
   int m_data[10000000];
};

次に、2 つのインスタンスがあるとします。

BigArray a;
a[0]=1;//initializaation etc
BigArray b=a;

この時点で、この不変式が必要です

assert(a[0]==b[0]);

デフォルトの copy ctor はこの不変性を保証しますが、オブジェクト全体のディープ コピーを犠牲にします。このようなスピードアップを試みるかもしれません

struct BigArray{
   BigArray():m_data(new int[10000000]){}
   int  operator[](size_t i)const{return (*m_data)[i];}
   int& operator[](size_t i){return (*m_data)[i];}
private:
   shared_ptr<int> m_data;
};

これは、ディープ コピーを作成せずに不変条件にも適合するため、これまでのところすべて問題ありません。この新しい実装を使用して、

b[0]=2;

ここで、これをディープ コピーのケース assert(a[0]!=b[0]); と同じように動作させたいと考えています。しかし、それは失敗します。これを解決するには、わずかな変更が必要です。

struct BigArray{
       BigArray():m_data(new int[10000000]){}
       int  operator[](size_t i)const{return (*m_data)[i];}
       int& operator[](size_t i){
          if(!m_data.unique()){//"detach"
            shared_ptr<int> _tmp(new int[10000000]);
            memcpy(_tmp.get(),m_data.get(),10000000);
            m_data=_tmp;
          }
          return (*m_data)[i];
       }
    private:
       shared_ptr<int> m_data;
    };

これで、const アクセスのみが必要な場合は浅くコピーされ、非 const アクセスが必要な場合はディープ コピーされるクラスができました。これが、「shared_data」ポインターの概念の背後にある考え方です。const呼び出しはディープ コピーしません (「デタッチ」と呼びます) が、非 const は共有されている場合はディープ コピーします。また、 operator== の上にいくつかのセマンティクスを追加して、ポインターだけでなくデータも比較するようにして、これが機能するようにします。

BigArray b=a;//shallow copy
assert(a==b);//true
b[0]=a[0]+1;//deep copy
b[0]=a[0];//put it back
assert(a==b);//true

この手法は COW (Copy on Write) と呼ばれ、C++ の黎明期から存在しています。また、非常に脆弱です。上記の例は、小さく、ユースケースが少ないため、うまくいくようです。実際には、面倒なことはめったになく、実際、C++0x は COW 文字列を非推奨にしています。したがって、注意して使用してください。

于 2010-04-17T13:58:11.420 に答える
1

最初のケースでは、ポインターに間接的なレベルを追加して、スマート ポインターによって表されるオブジェクトが元のポインターをラップするようにします。オブジェクトへのポインターは 1 つしかなく、元のポインターへの参照を追跡するのはラッパーの仕事です。非常に単純化したコードは次のようになります。

template<typename T>
struct smart_ptr {
    T    *ptr_to_object;
    int  *ptr_to_ref_count;
};

構造体をコピーするとき、コピー/割り当てコードは、参照カウントがインクリメントされる (またはオブジェクトが破棄された場合はデクリメントされる) ことを確認する必要がありますが、実際のラップされたオブジェクトへのポインターは決して変更されず、浅いコピーが可能です。構造体は非常に小さいため、コピーは簡単で安価であり、「すべて」必要なのは参照カウントを操作することだけです。

2 番目のケースでは、オブジェクト リポジトリのように見えます。FooWidget「暗黙的に共有」の部分は、フレームワークに次のようなことを要求する可能性があることを示唆しています。BarFoo.getFooWidget()たとえスマートであろうとなかろうと、返されるポインターが新しいオブジェクトへのポインターであるように見えても、実際には渡されています。ある種のオブジェクト キャッシュに保持されている既存のオブジェクトへのポインター。その意味では、ファクトリ メソッドを呼び出して取得するシングルトンのようなオブジェクトに似ているかもしれません。

少なくとも私にはその違いのように聞こえますが、あまりにも的外れで、戻るには Google マップが必要になるかもしれません。

于 2010-04-17T08:50:26.040 に答える