2011-09-12 197 views
12

我最近一直在學習託管指針,並遇到以下情況。堆棧對象的C++ shared_ptr

我正在實現遊戲視圖的模型/控制器類。我的觀點,將在模型中渲染事物。非常直截了當。在我的主要功能,我實例所有三個這樣的:

RenderModel m; 
m.AddItem(rect); // rect gets added just fine, it's an "entity" derivee 
RenderView v; 
v.SetModel(m); 

我的渲​​染視圖類是非常簡單的:

class RenderView 
{ 
public: 
explicit RenderView(); 
~RenderView(); 

void Update(); 

void SetModel(RenderModel& model); 

private: 
// disable 
RenderView(const RenderView& other); 
RenderView& operator=(const RenderView& other); 

// private members 
boost::scoped_ptr<RenderModel> _model; 
}; 

爲的setView的實現是非常標準:

void RenderView::SetModel(RenderModel& model) 
{ 
    _model.reset(&model); 
} 

的關鍵是,該視圖將模型存儲在智能指針中。然而,主要的是,模型被分配在堆棧上。程序退出時,內存被刪除兩次。這是有道理的。我目前的理解告訴我,任何存儲在smart_ptr(任何類型)中的東西都不應該分配在堆棧上。

經過上述所有設置後,我的問題很簡單:我如何規定參數未分配到堆棧上?是否接受智能指針作爲參數唯一的解決方案?即使這樣用我的視圖類不能做一些不正確,例如我不能保證別人:

// If I implemented SetModel this way: 
void RenderView::SetModel(const std::shared_ptr<RenderModel>& model) 
{ 
    _model.reset(&*model); 
} 

RenderModel m; 
RenderView v; 
std::shared_ptr<RenderModel> ptr(&m); // create a shared_ptr from a stack-object. 
v.SetModel(ptr); 

回答

8

我怎麼決定了一個參數沒有在棧上分配的?

是的,要求來電者提供std::shared_ptr<RenderModel>。如果來電者錯誤地構造了std::shared_ptr,那就是來電者的問題,而不是你的問題。

如果您打算爲RenderView是一個特定RenderModel的唯一擁有者,可以考慮具有該功能需要std::unique_ptrstd::auto_ptr代替;通過這種方式很明顯,調用者在調用該函數後不應該保留該對象的所有權。

另外,如果RenderModel便宜複製,使得它的一個副本,並使用副本:

_model.reset(new RenderModel(model)); 
+0

如果調用者誤解了shared_ptr,我認爲沒有辦法來檢測這樣的事情? 我簡單地問,因爲我覺得我可能再犯這個錯誤,我不想花費數小時重新調試問題。我可以在自己的顯示器上留下自己的便條,直到它燒到我的頭上... – Short

+0

有一些已知的'技巧'來檢測對象是否存在於堆棧或堆中,如比較地址。所有這些都會導致未定義的或實現相關的行爲。我想如果只是爲了暗示它可能有用,但它們不是真正的解決方案。 –

+3

不,如果你的函數需要一個'std :: shared_ptr',那麼就無法判斷這個指針是否指向一個有效的對象。也就是說,如果它明確需要一個'std :: shared_ptr',那就很難搞砸了(從本地變量構造一個'std :: shared_ptr'的代碼看起來一見不順,很容易避免)。 –

3

你或許應該定義類的語義更清楚。如果你想讓RenderView成爲RenderModel的所有者,它應該自己創建它(也許在構造函數中獲得一些工廠使用的標識符)。

我見過接收對象所有權的類,它明確定義了這些對象必須位於堆上,但就我看來,這種錯誤容易出錯,就像您現在遇到的錯誤一樣。你不能將一個堆棧對象放到一個智能指針上,這個智能指針希望它在堆上(因爲當它想要清除它時會使用刪除)。

+0

Qt使用上述模型,這是我從中獲得靈感的地方。它允許將多個視圖鏈接到一個模型。我將不得不更密切地關注他們的實施情況。 – Short

2

你描述你想要做什麼的方式是完全錯誤的。在MVP設計模式中,視圖不應直接訪問模型,而應將命令發送給演示者(通過調用演示者的函數)。

不管怎麼說,對方的回答你的問題:你的模型對象在堆上分配,像這樣:

std::shared_ptr<RenderModel> ptr(new RenderModel); 
RenderView v; 
v.SetModel(ptr); 

否則你的shared_ptr對象將嘗試刪除堆棧對象。

1

你應該回去思考設計。第一個代碼嗅覺是你正在通過引用獲取對象,然後試圖將其作爲智能指針進行管理。那是錯了

您應該首先決定誰負責資源並圍繞它進行設計,如果RenderView負責管理資源,那麼它不應接受引用,而應該接受(智能)指針。如果它是唯一所有者,如果所有權被稀釋(希望儘可能創建一個唯一所有者),那麼簽名應採用std::unique_ptr(或std::auto_ptr,如果您的編譯器+庫不支持unique_ptr),則使用shared_ptr

但也有一些其他場景中RenderView並不需要在所有管理資源,在這種情況下,它可以,如果它不能的RenderView壽命期間改變,以通過參考引用採取模型和存儲。在這種情況下,RenderView不負責管理資源,它不應該嘗試使用delete(包括通過智能指針)。

1
class ModelBase 
{ 
    //..... 
}; 

class RenderView 
{ 
    //.......... 
public: 
    template<typename ModelType> 
    shared_ptr<ModelType> CreateModel() { 
     ModelType* tmp=new ModelType(); 
     _model.reset(tmp); 
     return shared_ptr<ModelType>(_model,tmp); 
    } 

    shared_ptr<ModelBase> _model; 
    //...... 
}; 

如果模型類構造函數有參數,可以向CreateModel()方法中添加參數並使用C++ 11完美轉發工藝。

1

您應該要求用戶正確地通過輸入。首先將您的輸入類型更改爲智能指針(而不是參考變量)。其次,他們正確地將智能指針傳遞給一個什麼也不做的事情(例如下面的NoDelete)。

一個駭客法將檢查內存段。堆棧總是會從內核空間下降(我認爲32位的0xC0000000,類似於64位的0x7fff2507e800,基於下面的代碼)。所以你可以根據內存位置猜測它是否是堆棧變量。人們會告訴你它不是可移植的,但它是,除非你將在嵌入式系統上部署東西。

#include <iostream> 
#include <memory> 

using namespace std; 

class foo 
{ 
    public: 
    foo(shared_ptr<int> in) { 
     cerr << in.get() << endl; 
     cerr << var.use_count() << endl; 
     var = in; 
     cerr << var.use_count() << endl; 
    }; 

    shared_ptr<int> var; 
}; 

struct NoDelete { 
    void operator()(int* p) { 
     std::cout << "Not Deleting\n"; 
    }; 
}; 

int main() 
{ 
    int myval = 5; 

    shared_ptr<int> valptr(&myval, NoDelete()); 
    foo staticinst(valptr); 

    shared_ptr<int> dynptr(new int); 
    *dynptr = 5; 
    foo dynamicinst(dynptr); 
} 
0

簡而言之:定義自定義刪除器。 在堆疊物體上一個智能指針的情況下,可以構建智能指針與自定義刪除器,在這種情況下,「空刪除器」(或「StackObjectDeleter」)

class StackObjectDeleter { 
public: 
    void operator() (void*) const {} 
}; 

std::shared_ptr<RenderModel> ptr(&m, StackObjectDeleter()); 

的StackObjectDeleter取代default_delete如刪除對象。 default_delete只是簡單地調用delete(或delete [])。在StackObjectDeleter的情況下,什麼都不會發生。

+0

聖尼古拉蝙蝠俠! – Short