栈对象的c++shared_ptr

c++ shared_ptr of stack object

本文关键字:ptr c++shared 对象      更新时间:2023-10-16

我最近一直在学习托管指针,遇到了以下场景。

我正在为游戏视图实现一个模型/控制器类。我的视图,将渲染模型中的内容。很直接。在我的主要函数中,我像这样实例化所有三个:

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

如何指定堆栈上没有分配参数?

是,要求呼叫者提供std::shared_ptr<RenderModel>。如果调用者误解了std::shared_ptr,那是调用者的问题,而不是你的问题。

如果您希望RenderView成为特定RenderModel的唯一所有者,请考虑让该函数使用std::unique_ptrstd::auto_ptr;通过这种方式,很明显,调用者在调用函数后不应该保留对象的所有权。

或者,如果RenderModel的复制成本较低,则制作一份副本并使用该副本:

_model.reset(new RenderModel(model));

您可能应该更清楚地定义类的语义。如果你想让RenderView成为RenderModel的所有者,它应该自己创建它(也许可以在构造函数中获得一些与工厂一起使用的标识符(。

我见过接收对象所有权的类,并且明确定义了这些对象必须在堆上,但在我看来,这很容易出错,就像您现在遇到的错误一样。不能将堆栈对象提供给希望它在堆上的智能指针(因为当它想要清理它时,它会对它使用delete(。

您描述想要做什么的方式是完全错误的。在MVP设计模式中,视图不应直接访问模型,而应向演示者发送命令(通过调用演示者的函数(。

不管怎样,其他人已经回答了你的问题:你的模型对象必须在堆上分配,就像这样:

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

否则,sharedptr对象将尝试删除堆栈对象。

您应该回过头来思考设计。第一个代码气味是通过引用获取对象,然后尝试将其作为智能指针进行管理。这是错误的。

您应该从决定谁负责资源开始,并围绕资源进行设计,如果RenderView负责资源的管理,那么它不应该接受引用,而应该接受(智能(指针。如果它是唯一所有者,则签名应采用std::unique_ptr(如果编译器+库不支持unique_ptr,则为std::auto_ptr(,如果所有权被稀释(尽可能使其成为唯一所有者(,则使用shared_ptr

但也有其他场景,RenderView根本不需要管理资源,在这种情况下,如果在RenderView的生命周期内无法更改,它可以通过引用获取模型并通过引用存储。在这种情况下,RenderView不负责资源的管理,它不应该尝试delete(包括通过智能指针(。

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完美的转发技术。

您应该要求用户正确地传递输入。首先将输入类型更改为智能指针(而不是引用变量(。其次,让他们用一个什么都不做的正确传递智能指针(例如,下面的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 Deletingn";
    };
};
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);
}

简而言之:定义自定义deleter。在堆栈对象上的智能指针的情况下,可以使用自定义删除程序构造智能指针,在这种情况下是"Null deleter"(或"StackObjectDeleter"(

class StackObjectDeleter {
public:
    void operator () (void*) const {}
};
std::shared_ptr<RenderModel> ptr(&m, StackObjectDeleter());

StackObjectDeleter将default_delete替换为deleter对象。default_delete只是调用delete(或delete[](。在StackObjectDeleter的情况下,不会发生任何事情。