我最近一直在学习托管指针并遇到以下情况。
我正在为游戏视图实现模型/控制器类。我的观点,将在模型中呈现内容。挺直的。在我的main函数中,我将这三个实例化为:
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);
}
关键是,视图将模型存储在智能指针中。但是在main中,模型是在堆栈上分配的。程序退出时,内存将被删除两次。这是有道理的。我目前的理解告诉我,任何存储在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);
答案 0 :(得分:8)
如何指示参数未在堆栈上分配?
是的,要求来电者提供std::shared_ptr<RenderModel>
。如果来电者误解了std::shared_ptr
,这就是来电者的问题,而不是你的问题。
如果您打算让RenderView
成为特定RenderModel
的唯一所有者,请考虑让该功能改为std::unique_ptr
或std::auto_ptr
;这样很明显,调用者在调用函数后不应该保留对象的所有权。
或者,如果复制RenderModel
便宜,请复制它并使用副本:
_model.reset(new RenderModel(model));
答案 1 :(得分:3)
您应该更清楚地定义班级的语义。如果你想让RenderView成为RenderModel的所有者,它应该自己创建它(可能在构造函数中获得一些用于工厂的标识符)。
我已经看到了接收对象所有权的类,并且明确定义了这些对象必须在堆上,但我认为这很容易出错,就像你现在遇到的错误一样。你不能将一个堆栈对象提供给一个希望它在堆上的智能指针(因为当它想要清理它时会在它上面使用delete)。
答案 2 :(得分:2)
你描述你想做什么的方式是完全错误的。在MVP设计模式中,视图不应直接访问模型,而应将命令发送到演示者(通过调用演示者的函数)。
无论如何,其他人已经回答了你的问题:你的模型对象必须在堆上分配,如下所示:
std::shared_ptr<RenderModel> ptr( new RenderModel );
RenderView v;
v.SetModel(ptr);
否则你的shared_ptr对象将尝试删除堆栈对象。
答案 3 :(得分:1)
你应该回去思考一下设计。第一个代码气味是您通过引用获取对象,然后尝试将其作为智能指针进行管理。那是错误的。
您应该首先决定谁负责资源,并围绕它进行设计,如果RenderView
负责管理资源,那么它不应该接受引用,而是接受(智能)指针。如果它是唯一所有者,则签名应采用std::unique_ptr
(或std::auto_ptr
如果您的编译器+库不支持unique_ptr
),如果所有权被稀释(更喜欢制作一个尽可能的所有者),然后使用shared_ptr
。
但是还有其他情况RenderView
根本不需要管理资源,在这种情况下,如果在{的生命周期内无法更改它,它可以通过引用获取模型并通过引用存储它{1}}。在这种情况下,RenderView
不负责管理资源,它不应该尝试RenderView
它(包括通过智能指针)。
答案 4 :(得分: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完美转发技术。
答案 5 :(得分:1)
您应该要求用户正确传递输入。首先将输入类型更改为智能指针(而不是引用变量)。第二个让他们正确地传递智能指针与没有做任何事情(下面的NoDelete,例子)。
一种hackish方法是检查内存段。堆栈总是从内核空间开始增长(我认为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);
}
答案 6 :(得分:0)
简而言之:定义自定义删除器。 如果堆栈对象上有智能指针,您可以使用自定义删除器构造智能指针,在本例中为“Null Deleter”(或“StackObjectDeleter”)
class StackObjectDeleter {
public:
void operator () (void*) const {}
};
std::shared_ptr<RenderModel> ptr(&m, StackObjectDeleter());
StackObjectDeleter将default_dele替换为删除对象。 default_delete只调用delete(或delete [])。在StackObjectDeleter的情况下,什么都不会发生。