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