c ++ shared_ptr стекового объекта - PullRequest
       10

c ++ shared_ptr стекового объекта

13 голосов
/ 12 сентября 2011

В последнее время я изучал управляемые указатели и столкнулся со следующим сценарием.

Я реализую класс модель / контроллер для просмотра игры. На мой взгляд, будет рендерить вещи в модели. Довольно прямо вперед. В своей основной функции я создаю все три экземпляра следующим образом:

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

Ответы [ 7 ]

8 голосов
/ 12 сентября 2011

как мне указать, что параметр не был выделен в стеке?

Да, требуется, чтобы вызывающая сторона указала std::shared_ptr<RenderModel>.Если вызывающий объект неправильно истолковывает std::shared_ptr, это проблема вызывающего, а не ваша.

Если вы намереваетесь, чтобы RenderView был единственным владельцем определенного RenderModel, рассмотрите возможность использования функции std::unique_ptr или std::auto_ptr вместо;Таким образом, становится ясно, что вызывающая сторона не должна сохранять права собственности на объект после вызова функции.

В качестве альтернативы, если копирование RenderModel обходится дешево, сделайте его копию и используйте копию:

_model.reset(new RenderModel(model));
3 голосов
/ 12 сентября 2011

Вероятно, вам следует более четко определить семантику вашего класса. Если вы хотите, чтобы RenderView был владельцем RenderModel, он должен создать его самостоятельно (может быть, получить в конструкторе какой-то идентификатор для использования с фабрикой).

Я видел классы, которые получают права собственности на объекты, и было четко определено, что эти объекты должны быть в куче, но это, на мой взгляд, подвержено ошибкам, точно так же, как ошибка, с которой вы сейчас столкнулись. Вы не можете передать объект стека интеллектуальному указателю, который ожидает, что он будет в куче (потому что он будет использовать delete для него, когда он хочет очистить его).

2 голосов
/ 12 сентября 2011

То, как вы описали, что вы хотите сделать, совершенно неверно.В шаблоне проектирования MVP представление не должно напрямую обращаться к модели, но должно посылать команды докладчику (вызывая функции докладчика).

В любом случае, другие ответили на ваш вопрос: объект вашей модели должен быть выделенв куче, например:

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

в противном случае ваш объект shared_ptr попытается удалить объект стека.

1 голос
/ 01 февраля 2014

Вы должны потребовать, чтобы пользователь правильно прошел ввод Сначала измените ваш тип ввода на интеллектуальный указатель (а не ссылочную переменную). Во-вторых, пусть они правильно передадут умный указатель, который ничего не делает (NoDelete ниже, пример).

Хакерским методом будет проверка сегмента памяти. Стек всегда будет расти из пространства ядра (я думаю, что 0xC0000000 в 32-битной версии, что-то вроде 0x7fff2507e800 в 64-битной системе, на основе приведенного ниже кода). Таким образом, вы можете догадаться, основываясь на расположении памяти, является ли она переменной стека или нет. Люди скажут вам, что он не переносимый, но это так, если только вы не собираетесь развертывать компоненты во встроенных системах.

#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);
}
1 голос
/ 12 сентября 2011
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 голос
/ 12 сентября 2011

Вы должны вернуться и подумать о дизайне. Первый запах кода заключается в том, что вы берете объект по ссылке, а затем пытаетесь управлять им как умным указателем. Это неправильно .

Вы должны начать с того, чтобы решить, кто отвечает за ресурс, и спроектировать его, если RenderView отвечает за управление ресурсом, то он должен принимать не ссылку, а скорее (умный) указатель. Если это единственный владелец, подпись должна принимать std::unique_ptr (или std::auto_ptr, если ваш компилятор + библиотеки не поддерживают unique_ptr), если владение разжижено (предпочитайте делать единственного владельца, когда это возможно), тогда используйте shared_ptr.

Но есть и другие сценарии, в которых RenderView вообще не нужно управлять ресурсом, и в этом случае он может взять модель по ссылке и сохранить ее по ссылке, если ее нельзя изменить в течение срока действия RenderView , В этом сценарии, где RenderView не отвечает за управление ресурсом, он не должен пытаться delete когда-либо использовать его (в том числе с помощью интеллектуального указателя).

0 голосов
/ 04 июля 2017

Короче говоря: определить пользовательские удаления В случае интеллектуального указателя на объект стека вы можете создать интеллектуальный указатель с помощью пользовательского средства удаления, в данном случае «Null Deleter» (или «StackObjectDeleter»)

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

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

StackObjectDeleter заменяет default_delete как объект удаления. default_delete просто вызывает delete (или delete []). В случае StackObjectDeleter ничего не произойдет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...