2011-12-01 63 views
4

我想了解以下(讓我們假裝MyStorageClass是巨大的):返回指針的載體 - 瞭解

class MyStorageClass 
{ 
public: 
    string x; 
    string y; 
    string z; 
}; 

class storage 
{ 
public: 
    storage(); 
    ~storage() 
    { 
    vector<MyStorageClass *>::iterator it = myVec.begin(); it != myVec.end(); it++) 
    { 
     delete *it; 
    } 

    vector<MyStorageClass*> getItems() 
    { 
    for(int i = 0; i < 10; ++i) 
    { 
     v.push_back(new MyStorageClass()); 
    } 
    return v; 
    } 

private: 
    vector<MyStorageClass*> v; 

}; 



main() 
{ 
    storage s; 
    vector<MyStorageClass*> vm = s.getItems(); 
} 

從我understading,當s返回向量,並分配給vm這是爲完成一份副本(按價值)。因此,如果s超出範圍並將其稱爲析構函數,則vm擁有自己的副本,並且其結構不受影響。但是,傳遞價值並不高效。所以,如果你改變它按引用傳遞:

vector<MyStorageClass*>& getItems() 
{ 
    for(int i = 0; i < 10; ++i) 
    { 
    v.push_back(new MyStorageClass()); 
    } 
    return v; 
} 

您傳遞的v的存儲位置(在課堂上存儲)。但是您仍然使用=運算符將它的副本分配給Main類中的矢量vm。因此,vm獨立於v,並且如果Storage析構函數被調用vm不受影響。

最後,如果getItems返回的引用,在主你有以下幾點:

main() 
{ 
    storage s; 
    vector<MyStorageClass*> &vm = s.getItems(); 
} 

現在,vm持有v地址。所以它受存儲析構函數影響。

問題:我上面說過的是真的嗎?

+0

聲音對我來說是正確的。沒有一個答案,因爲我沒有正式覈實。 –

+0

找不到這個:) :) – LiMuBei

+1

'myVec'沒有在任何地方聲明。 '〜存儲'中是否指'v'? – outis

回答

7

是的,您已經正確理解了這一點。現在,如果你想把它提升到一個新的水平,首先找到爲什麼這是不好的原因:

  1. 返回指針或對內部類的引用打破封裝。
  2. 一旦對應的s對象被銷燬,您獲得的參考將成爲懸掛參考,從而打破引用總是有效的不變量。
  3. 通過返回一個指針向量,您可以讓呼叫者知道天氣,他不得不刪除這些指針。

一個更好的解決辦法是storage揭露,從s返回相應的迭代方法beginend。或者,您可以使用Visitor模式來運行需要在s上運行的算法。

另外,在你的情況,它似乎像矢量s應該擁有它包含的對象。這將是使用boost::ptr_vector的一個很好的指標。

+0

+1,因爲我第一次投球時提到晃來晃去:) –

0

我想你說的是真的,但我對目的有點困惑。

vector<MyStorageClass*> &vm = s.getItems(); 

通過參考獲得矢量。但是矢量包含指針,所以超出範圍的矢量不會導致任何析構函數在第一個位置運行 - 析構函數只有在它們是某種類型的智能指針時纔會運行(甚至是依賴它)。

所以,你可以愉快地傳遞一個指針的向量,而不會有太大的問題。我相信你會通過參考來節省一些效率,但我認爲它不如你想象的那麼嚴重。另外,你的指向對象是用新的(動態的)創建的,所以你可以用同樣的方式通過值返回矢量,而不用擔心丟失指向對象的指針。

所以,我認爲你的邏輯是好的,你的參考方式更有效一些,但我只是想確保你知道它可以在沒有問題的情況下以兩種方式工作:)(並且因爲它的一個指針向量,按值也不算太差)。 PS:從參考的角度來看,你不必擔心懸念,比上面Bjorn指出的更令人沮喪。如果你曾經使用過string.c_str(),你可能已經體會到了這一點。如果從字符串中獲得.c_str()並且原始字符串超出作用域,那麼由.c_str()返回的指針是懸空的(指向不再用於此目的的內存),並且訪問它會導致未定義的行爲。因此,按價值可能是更好的選擇(但它取決於你的設計 - 例如,如果這將是一個單一的持續在你的應用程序的持續時間,懸空可能不是一個問題)。

0

即使向量複製其值,您的情況下的值是指針,而不是指向的對象。所以「vm有自己的副本」並非完全正確:第一段代碼中的結果向量將具有指針的副本,但不包含指向的MyStorageClass對象的副本;所以,實際上,在所有3個代碼示例中,如果調用Storage析構函數,存儲在vm中的指針將不再有效!

但是,如果您需要確保Storage在最後一次訪問某個MyStorageClass對象之前未被銷燬,那麼在所提供的方法中,第三種方法將是首選方法,因爲向量數據(即指針)在內存中只有一次。您應該考慮返回const向量,但是,每個getItems的調用者都可以通過返回的引用修改Storage類的v向量。如其他人已經指出的那樣,揭示矢量本身可能並不是一個好主意,你可能會考慮使用迭代器或者訪問者模式。

此外,指針的使用 - 在不是絕對需要的情況下 - 在較大的項目中通常頗爲皺眉。考慮使用智能指針,如std::auto_ptr(雖然由於複製語義奇怪而不太推薦),或者更易於理解的boost::shared_ptr/std::tr1::shared_ptr。作爲一個附註,第一個和第二個代碼示例(至少在大多數現代編譯器中)具有完全相同的性能,因爲在第一種情況下,編譯器可以優化臨時返回值(請查看「返回值優化「)。

0

請注意,如果複製類存儲的對象,您的存儲類將導致問題。由於您不提供複製構造函數或賦值運算符,因此將使用默認值。默認會盲目地複製指針向量,現在你有兩個存儲對象都會嘗試刪除向量中的指針。

0

從我的理解,當s返回向量,並分配給vm這是作爲一個副本(按價值計算)來完成。因此,如果s超出範圍並將其稱爲析構函數,則vm擁有自己的副本,並且其結構不受影響。

普通指針(T*)不跟std::vector的拷貝構造函數或賦值運算符(雖然你可以使用一個智能指針類,將複製)。由於指針指向的內容(「指向的」)未被複制,所以複製操作是淺拷貝。雖然對s.v的操作不會影響vm(反之亦然),但影響從一個訪問的指針的任何操作都會影響另一個,因爲它們是相同的。

如果你要適當實施的智能指針存儲在s.v而非MyStorageClass*,標準std::vector複製操作可能會產生深刻的副本,使s.v內容可以在不影響以任何方式vm內容被改變(與反之亦然)。複製指針的複製操作將使用指向類的複製操作來複制指向的對象。結果,每個尖端對象只會被一個拷貝指針指向。

或者(正如其他人提到的那樣),您可以使用智能指針,該智能指針允許共享所有權並讓其管理尖銳對象,從而消除~storage中的delete調用。

但是,傳遞值並不高效。

但是,由於它是,在某些情況下是正確的,特別是當變異容器的一個實例必須不影響其他(你提到的情況下),在這種情況下,你無法脫身無副本。正確的王牌效率。一般來說,您可以使用copy-swap idiom來減少由於臨時創建造成的效率低下。據我所知,大多數STL實現不使用std::vector的copy-swap。 Copy-on-write,它只會在矢量更改時執行復制,也可以提供幫助。線程使copy-on-write複雜化,並可能導致效率低下。

現在,vm的地址爲v

vm不包含地址。雖然引用可能在引擎蓋下使用指針(它們也可能不是;根據C++ 03的第8.3.2-3節,它們可能甚至不需要額外的存儲),但在語言級別引用不是指針。考慮你可以有空指針,但不能有空引用。更準確的說法是,vm的別名爲vvmv指的是同一個對象),這就是爲什麼v上的操作將影響vm(反之亦然)的原因。