Инициализация и управление shared_ptr для базового интерфейса

У меня есть несколько вопросов, связанных с использованием shared_ptr, указывающего на базовый класс. Их ответы влияют друг на друга, и для всех трех мне нужен один и тот же фрагмент кода, чтобы установить контекст настолько минимально, насколько это возможно, например этот (все вопросы касаются Foo И его held_object_):

#include <memory>
#include <utility>

class Bar  // interface for Scene (see linked question)
{
public:
virtual ~Bar() = 0;
// more virtual methods to work with Scene
};

Bar::~Bar() = default;

class Baz : public Bar // interface for Foo
{
public:
virtual ~Baz() = 0;
// more virtual methods to work with Foo
};

Baz::~Baz() = default;

class ConcreteBaz : public Baz // actual thing I'm acquiring and releasing
{
// overrides and whatnot
};

class Foo
{
public:
void acquire(const std::shared_ptr<Baz>& object) {};
void release(/* actually takes a place to release into */) {};

private:
std::shared_ptr<Baz> held_object_;
};

int main()
{
auto foo = std::make_unique<Foo>();
auto p_foo = foo.get();

while (/* condition */)
{
auto cbaz = std::make_shared<ConcreteBaz>();
// Scene gets a copy here

p_foo->acquire(cbaz);
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);

// do other physical things, then acquire cbaz again

p_foo->acquire(cbaz);
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);
}
}

Как вы видете cbaz это указатель на полиморфную иерархию, которая может работать как с Scene а также Foo, ConcreteBaz на самом деле представляет собой физическое лицо который Foo может, как указано в коде, взять на себя ответственность (это shared_ptr потому что оба Scene а также Foo владеть им согласно этот. Я удалил детали Scene здесь, потому что это ОТ).

Вопросы

1) Как мне инициализировать held_object_? Могу ли я сделать это с одним выделением?

В действительности, foo не владеет cbaz пока он (физически) не получит его, поэтому конструктор должен инициализировать указатель на nullptr. Изначально я хотел использовать make_shared согласно этот но я читаю Вот что это инициализирует значение, то есть, если бы я должен был сделать

Foo::Foo()
: held_object_ {std::make_shared<Baz>()} // equivalent to std::make_shared<Baz>(nullptr)
{
}

компилятор будет жаловаться error: invalid new-expression of abstract class type, это было бы таким образом стать дубликатом этот вопрос, но принятый ответ либо оставляет shared_ptr неинициализирован или создает дальнейшее unique_ptr чтобы … в принципе неясно, как бы я применил это здесь, и я не думаю, что могу позвонить reset в конструкторе (?).

Должен ли я вместо этого быть явным и сказать

Foo::Foo()
: held_object_ {std::shared_ptr<Baz> {nullptr}}
{
}

не заботясь о двойном распределении, которое make_shared избегает? На данный момент это не будет почти точно так же, как просто : held_object_ {} (переместить ctor против ctor по умолчанию в сторону)?
РЕДАКТИРОВАТЬ: Могу ли я сделать это с помощью одного распределения, как make_shared делает?

2) Как мне управлять held_object_?

Прямо сейчас, чтобы получить право собственности на cbaz после звонка acquire Я в конечном итоге попасть в вызов метода

void Foo::setHeldObject_(std::shared_ptr<Baz> object)
{
this->held_object_ = std::move(object);
}

Однако в release Мне придется уничтожить владение shared_ptr (чтобы затем установить его снова, следующий цикл) и сделать состояние моего Foo экземпляр соответствует своему физическому состоянию IRL. Это заставило меня задуматься об использовании станд :: shared_ptr :: сброс (прежде чем даже прочитать предыдущий связанный ответ), потому что я бы заменил pointee на nullptr, если бы позвонил setHeldObject() и установить cbaz иначе. Однако я не могу определить правильный синтаксис для тела метода, как:

  • this->held_object_.reset(object); очевидно, неправильно, так как я хочу управлять указателем, а не указателем (и поэтому не компилируется);
  • this->held_object_.reset(&object); выглядит неправильно, и дает error: cannot convert ‘std::shared_ptr<Baz>*’ to ‘Baz*’ in initialization
  • this->held_object_.reset(object.get()); также кажется неправильным, потому что я не думаю, что я поднимаю user_count таким образом (хотя он компилируется).

РЕДАКТИРОВАТЬ: Я считаю, что это должно быть возможно сделать с тем же вызовом метода, давая или не давая аргумент. Будет ли это возможно с reset?

3) На самом деле есть только два владельца

main функция, которую я написал здесь, на самом деле Process учебный класс’ run метод, но его определение, как это делает его так, что use_count поднимает до 3 к тому времени Scene а также Foo есть свои shared_ptr, С каких пор я make_shared далее, я в цикле, более того, логика говорит, что должно быть только два владельца. Это вариант использования для weak_ptr где это может иметь смысл сделать что-то вроде:

int main()
{
auto foo = std::make_unique<Foo>();
auto p_foo = foo.get();

while (/* condition */)
{
auto cbaz = std::make_shared<ConcreteBaz>();
auto p_cbaz = std::weak_ptr<ConcreteBaz> {cbaz};
// Scene gets a && via std::move here

p_foo->acquire(p_cbaz.lock()); // this instantly gets converted to a shared_ptr EDIT: BUT IT DOESN'T INCREASE THE USE COUNT
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);

// do other physical things, then acquire cbaz again

p_foo->acquire(p_cbaz.lock()); // this instantly gets converted to a shared_ptr and would be nullptr had I moved in the first acquire call EDIT: BUT IT DOESN'T INCREASE THE USE COUNT
// do physical things to cbaz
p_foo->release(/* actually takes a place to release into */);
}
}

или есть лучший способ?

3

Решение

Как мне инициализировать held_object_

Если ты хочешь held_object_ быть nullptr тогда вам не нужно ничего делать. Компилятор сгенерировал конструктор по умолчанию Foo позвоню held_object_Конструктор по умолчанию, оставляющий его как нулевой указатель.

Как мне управлять held_object_

Когда вам нужно освободить владение указателем, вы просто звоните reset без каких-либо параметров. Это эффективно делает shared_ptr().swap(*this); который оставляет вас с нулевым указателем.

2

Другие решения

1) Как мне инициализировать удерживаемый_объект_?

Вы можете просто сделать это:

Foo::Foo()
: held_object_ {nullptr}
{
}

Но это, конечно, эквивалентно : held_object_ {}, хотя и чуть более явно в вашем намерении. Другой альтернативой будет иметь

class Foo
{
// ...
private:
std::shared_ptr<Baz> held_object_ = nullptr;
};

2) Как мне управлять hold_object_?

Ваша первая версия с std::move просто отлично. Обращаем ваше внимание, что таким образом вы приобретаете право собственности, но не получаете исключительное право собственности (поэтому я не назвал бы метод acquire).

3) На самом деле есть только два владельца

Я не думаю, что вы можете получить реальный ответ на этот вопрос, так как он зависит от вашей конкретной ситуации и (даже с учетом этого) в некоторой степени основывается на мнении. Кроме того, не беспокойтесь о количестве использований, если только вы не используете его для повышения производительности. Написание другого кода, потому что вы беспокоитесь о мелочах производительности (стоимость подсчета ссылок в shared_ptr) является преждевременной оптимизацией, если у вас нет веских оснований полагать (или доказывать), что это будет проблемой. Вы делаете это во внутреннем цикле? Если нет, то ложный пересчет вверх / вниз не будет ощущаться.

0

По вопросам рекламы [email protected]