c ++堆栈对象的shared_ptr

时间:2011-09-12 05:16:56

标签: c++ stack heap smart-pointers

我最近一直在学习托管指针并遇到以下情况。

我正在为游戏视图实现模型/控制器类。我的观点,将在模型中呈现内容。挺直的。在我的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);

7 个答案:

答案 0 :(得分:8)

  

如何指示参数未在堆栈上分配?

是的,要求来电者提供std::shared_ptr<RenderModel>。如果来电者误解了std::shared_ptr,这就是来电者的问题,而不是你的问题。

如果您打算让RenderView成为特定RenderModel的唯一所有者,请考虑让该功能改为std::unique_ptrstd::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的情况下,什么都不会发生。