Инициализация и управление 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. Это заставило меня задуматься об использовании std:: shared_ptr:: reset (прежде чем даже прочитать предыдущий связанный ответ), потому что я бы заменил 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 */);
  }
}

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

2 ответа

Решение

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

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

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

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

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

Другие вопросы по тегам