2010-04-17 553 views
9

在這個insightful article中,其中一位Qt程序員試圖解釋不同類型的智能指針Qt實現。在開始的時候,他讓共享數據之間的區別,並分享自己的指針:C++智能指針:共享指針與共享數據

首先,讓我們弄清楚一兩件事: 有共享 指針和共享數據之間的差異。當您共享指針 時,指針及其生命週期的值將由智能指針類保護 。在其他 單詞中,指針是不變的。 但是,指針 指向的對象完全在其控制範圍之外 。我們不知道 對象是否可複製,如果它是 可賦值或不可以。

現在,共享數據涉及 智能指針類知道 關於被共享的數據。實際上, 整點就是數據是 被共享,我們不在乎如何。 指針正被用於 共享數據的事實在 這一點上是不相關的。例如,你不要 真的在乎Qt工具類是如何隱含地共享的,你呢? 對你很重要的是他們共享 (從而減少內存消耗)和 ,他們的工作,就好像他們沒有。

坦率地說,我只是不介紹這個解釋。文章評論中有澄清的請求,但我沒有發現作者的解釋是足夠的。

如果你明白這一點,請解釋一下。這種區別是什麼,以及其他共享指針類(即來自boost或新的C++標準)如何適合此分類標準?

預先感謝

+0

這Qt的東西仍然讓我困惑:) – 2010-04-17 12:34:09

回答

1

在第一種情況下,在添加了一個間接層到指針,使得由智能指針所表示的對象包裝的原始指針。只有一個指向對象的指針,並且它是包裝器的工作,以跟蹤對原始指針的引用。代碼非常簡單的一點可能是這樣的:

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

當你複製的結構,你的複製/分配的代碼必須確保該引用計數遞增(或如果對象被銷燬遞減),但指向實際包裝對象的指針永遠不會改變,只能被淺拷貝。由於這個結構體非常小,複製起來既容易又便宜,而且你所要做的全部操作就是操縱引用計數。

在第二種情況下,它更像是一個對象存儲庫。 '隱式共享'部分暗示你可能會通過做一些類似於BarFoo.getFooWidget()的東西來詢問框架的FooWidget,並且即使它看起來像指針 - 不管是否聰明 - 您返回的是指向新對象的指針,實際上被遞交一個指向一個存在於某種對象緩存中的現有對象的指針。從這個意義上說,它可能更類似於通過調用工廠方法獲得的類似單例的對象。

至少這就是我的區別,但我可能離我很遠,我需要谷歌地圖找回我的路。

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]; 

共享數據並不一定意味着你必須向你的指針。您可以純粹使用和cout << a;來使用std::string,並且絕不會觸及任何c_str()函數。仍然可以在幕後繼續分享。同樣的事情也發生在很多Qt類和其他Widget工具包的類別上,這被稱爲隱式共享(或拷貝寫在)。所以我認爲可以這樣總結:

  • 分享指針:當我們複製智能指針時,我們總是指向相同的地址,這意味着我們共享指針值。
  • 分享數據:我們可能會在不同的時間指向不同的地址。暗示我們知道如何將數據從一個地址複製到另一個地址。

所以試圖分類

  • boost::shared_ptrboost::intrusive_ptr:共享指針,而不是數據。
  • QString,QPen,QSharedDataPointer:分享它包含的數據。
  • std::unique_ptrstd::auto_ptr(以及QScopedPointer):既不共享指針,也不是數據。
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]; 
}; 

現在說,我們有兩個實例:

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

在這一點上,我們希望這個不變

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

默認複印ctor確保這個不變,但是以犧牲深度拷貝e爲代價修復對象。我們可能會嘗試像這樣的加速

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; 

現在,我們希望這個工作一樣的深拷貝的情況下 斷言(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訪問時,深拷貝是淺複製。這是「shared_data」指針概念背後的想法。 const調用不會深度複製(他們稱之爲「分離」),而非const則在深度複製(如果共享)。它也增加了運營商的頂部一些語義==使得它不只是比較指針,但數據一樣,所以這將工作:

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(寫入時複製)和自從C++出現以來一直存在。它也非常脆弱 - 上面的例子似乎起作用,因爲它很小並且用例很少。在實踐中,它很少會遇到麻煩,事實上C++ 0x已經廢棄了COW字符串。所以謹慎使用。

+0

所以你暗示他分享數據的意思是COW,並通過共享指針是簡單的shared_ptr /引用計數指針成語? – 2010-04-17 14:37:55

+0

是的。如果你閱讀QT文檔的話也是如此。 http://doc.trolltech.com/4.5/qshareddatapointer.html grep「copy on write」 – 2010-04-18 02:08:33