Подтвердить что ты не робот

С++ shared_ptr объекта стека

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

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

RenderModel m;
m.AddItem(rect); // rect gets added just fine, it 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);
4b9b3361

Ответ 1

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

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

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

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

_model.reset(new RenderModel(model));

Ответ 2

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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() и использовать совершенную технику пересылки С++ 11.

Ответ 6

Вы должны потребовать от пользователя правильного ввода ввода. Сначала измените тип ввода на интеллектуальный указатель (а не на ссылочную переменную). Во-вторых, им надлежащим образом передать интеллектуальный указатель с тем, что ничего не делает (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);
}

Ответ 7

Вкратце: укажите пользовательский делектор. В случае умного указателя на объект стека вы можете построить интеллектуальный указатель с пользовательским удалением, в этом случае "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 ничего не произойдет.