Инициализация и управление shared_ptr для базового интерфейса - PullRequest
0 голосов
/ 04 декабря 2018

У меня есть несколько вопросов, связанных с использованием shared_ptr, указывающего на базовый класс.Их ответы влияют друг на друга, и для всех трех мне нужен один и тот же фрагмент кода, чтобы установить минимально возможный контекст, например this (все вопросы относятся к 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, потому что это OT).

Вопросы

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

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

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.Начиная с I 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 ]

0 голосов
/ 04 декабря 2018

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

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

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

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

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

2) Как мне управлять удерживаемым_объектом _?

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

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

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

0 голосов
/ 04 декабря 2018

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

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

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

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

...