Предположим, у меня был класс Manager, который содержал вектор некоторого объекта:
class SomeObjectManager
{
private:
std::vector<SomeObject> _heldObjects;
};
И в этом классе у меня была какая-то функция, которая перебирала указанный вектор для возврата запрошенного объекта.
SomeObject getSomeObjectByName(std::string nameToFind);
Что мне нужно знать, так это когда уместно использовать умные указатели. Должен ли я на самом деле возвращать что-то вроде ниже?
std::shared_ptr<SomeObject> getSomeObjectByName(std::string nameToFind);
Или я должен использовать что-то еще, как unique_ptr или weak_ptr? Я хочу, чтобы класс SomeObjectManager владел реальным возвращаемым объектом и никогда не говорил, что SomeObject будет удален, если только Менеджер не сделает это.
Я только недавно вернулся в мир C ++ после долгого времени нахождения в режиме C #; спасибо за помощь и прояснение моего замешательства.
Я много читал об этом, но так и не нашел прямого ответа на мою конкретную ситуацию.
Правка № 1
Я хотел бы перефразировать мои последние несколько предложений с этим:
Я хочу, чтобы класс SomeObjectManager владел реальным возвращаемым объектом и никогда не говорил, чтобы SomeObject удалялся из вектора и впоследствии удалялся, выпадал из области видимости, пока Менеджер не заставит его это сделать. Например:
void SomeObjectManager::removeSomeObjectByName(const std::string& objectToRemove);
Это будет просто перебрать вектор, найдя сказал SomeObject
и удалите его из вектора.
поскольку SomeObjectManager
это владелец из SomeObject
экземпляры (хранятся в его std::vector
член данных) я бы просто вернул сырье указатели, так как они на самом деле наблюдения указатели.
std::vector<SomeObject> _heldObjects;
SomeObject* getSomeObjectByName(const std::string& nameToFind) {
... find index of object corresponding to 'nameToFind'
return &_heldObjects[foundIndex];
}
(Обратите внимание, что я прошел nameToFind
с помощью ссылка на const
, так как я предполагаю, что nameToFind
является входной строкой, так что если внутри метода вы просто наблюдения эту строку, вы можете избежать глубоких копий, используя const &
).
Вы должны обратить внимание, когда у вас есть владеющим необработанные указатели (они должны быть обернуты в безопасные границы RAII), но наблюдения сырые указатели в порядке.
Просто убедитесь, что срок службы SomeObjectManager
превосходит клиентов, чтобы убедиться, что клиенты ссылаются на действительные объекты.
Также обратите внимание, что если вы добавляете новые элементы в элемент векторных данных (например, используя std::vector::push_back()
), адреса предыдущего SomeObject
экземпляры, хранящиеся в векторе, могут измениться. Таким образом, если вы дали указатели для тех, кто снаружи, они становятся недействительными.
Итак, убедитесь, что размер вектора и векторное содержимое не изменились, прежде чем указывать на его элементы клиентский код снаружи.
Альтернативой будет иметь std::vector<std::unique_ptr<SomeObject>>
как член данных. В этом случае, даже если вектор был изменен, адреса, которые вы вернули, используя умные указатели (в частности, используя std::unique_ptr::get()
) еще действительны:
std::vector<std::unique_ptr<SomeObject>> _heldObjects;
SomeObject* getSomeObjectByName(const std::string& nameToFind) {
... find index of object corresponding to 'nameToFind'
return _heldObjects[foundIndex].get();
}
PS
Другой вариант может быть возвращение Рекомендации в const SomeObject
(при условии, что это использование const
имеет смысл в вашем дизайне):
std::vector<SomeObject> _heldObjects;
const SomeObject& getSomeObjectByName(const std::string& nameToFind) const {
... find index of object corresponding to 'nameToFind'
return _heldObjects[foundIndex];
}
Если ваша программа работает в одна нить, вы в основном хороши с возвратом необработанных указателей или ссылок на объекты, которые хранятся в vector
, если у вас достаточно дисциплины.
Поскольку менеджер находится в частной собственности vector
и объекты внутри, и, следовательно, контролируют, когда объекты удаляются, вы можете убедиться, что не осталось недопустимых указателей на объекты, которые были удалены (это не гарантируется автоматически!).
По сути, менеджер должен когда-либо удалять объекты только тогда, когда он знает, что никто не хранит ссылку на этот объект, например, делая это только в определенные, четко определенные моменты времени (например, в конце программы или когда он знает, что потребители не остаются, или такой).
Подсчет ссылок является одним из способов сделать это, и это то, что shared_ptr
внутренне тоже (ну нет … строго к письму спецификации, подсчет ссылок не требуется, определяется только видимое поведение, но practially все реализации делают это).
Процесс «удаления» объекта, таким образом, просто уменьшит счетчик ссылок (так же, как в управляемом языке), и объект будет действительно перестать существовать, когда счетчик ссылок достигает нуля. Который наверное но не обязательно сразу тот случай, когда вы его «удалите». С тем же успехом может пройти несколько минут, прежде чем объект будет фактически уничтожен.
Этот подход работает «автоматически» без большой усердия и строгой дисциплины, и его можно реализовать, просто запоминая shared_ptr
с вашими объектами в векторе и возвращая либо shared_ptr
с или weak_ptr
s.
Подождите минутку! Тогда почему вообще weak_ptr
Если вы можете просто вернуть shared_ptr
тоже? Они делают разные вещи, как логически, так и практически. shared_ptr
с собственными (по крайней мере, частично), и weak_ptr
нет. Также, weak_ptr
s дешевле копировать.
В многопоточное программа, хранение shared_ptr
и возвращая weak_ptr
это единственный безопасный подход. weak_ptr
не владеет ресурсом и, следовательно, не может помешать администратору удалить объект, но дает владельцу надежный и однозначный способ узнать, является ли ресурс действительным и что ресурс останется в силе пока ты им пользуешься.
Вы возвращаете это weak_ptr
и когда потребитель действительно хочет использовать объект, он преобразует weak_ptr
на временный shared_ptr
, Это либо не удастся (давая нулевой указатель), так что потребитель знает, что объект был удален, и он может не использовать его. Или это удастся, и теперь потребитель имеет действительный указатель с общим владением объектом, который теперь гарантированно оставаться в силе пока он используется.
Нет ничего между «действительным» и «недействительным», нет догадок и ничего, что может потерпеть неудачу. Если вы успешно преобразовали в действительный временный shared_ptr
, ты в порядке. В противном случае объект исчез, но ты знаешь что.
Это большой, большой плюс с точки зрения безопасности. Даже если менеджер «удалит» объект, пока вы его используете, ваша программа не будет аварийно завершать работу или создавать мусор, объект остается действительным, пока вы не прекратите его использовать!
Возможно, это несколько размывает парадигму «менеджер удаляет объекты, когда он решит это сделать», но на самом деле это единственный способ сделать это безопасно. Менеджер по-прежнему контролирует, какие объекты удалять, он не может удалить только объект немедленно в то время как это используется (что могло бы привести к ужасному бедствию). Однако он может в любое время запланировать удаление на следующий возможный момент, удалив его shared_ptr
и, таким образом, уменьшить счетчик ссылок.
Единственным очевидным примером является случай, когда объект должен быть уничтоженным немедленно (потому что деструктор имеет побочные эффекты, которые должны произойти немедленно, без задержки). Но в этом случае очень трудно (кошмар!) Получить права одновременного доступа. К счастью, это тоже очень редкий сценарий.
Верните ссылку (или обычный указатель) на SomeObject из вашей функции. Ссылка действительна до тех пор, пока она остается в векторе, а вектор не перераспределяется (осторожно, возможно, вместо этого используйте список или вектор unique_ptr). При удалении из вектора объект мертв, и все ссылки на него больше не действительны. (Опять осторожно извлекая элемент посередине)
Если вы не храните ваши объекты как std :: shared_ptrs, то возвращать std :: shared_ptr не имеет смысла. Даже не уверен, как вы собираетесь это сделать. Я не думаю, что есть способ обернуть уже существующий указатель в умный указатель. Если у вас уже есть данные, вы можете просто вернуть на них обычный константный указатель. Таким образом вы избежите накладных расходов, необходимых для копирования содержимого объекта.
У вас есть выбор использования shared_ptr
или же weak_ptr
в этом случае. То, что вы используете, будет зависеть от того, сколько времени вы хотите для объекта.
Если вы хотите, чтобы объект был действительным, пока SomeObjectManager
имеет ссылку на него, и клиент использует его в то время, а затем использовать weak_ptr
, Если вы хотите, чтобы ссылка оставалась действительной, если либо SomeObjectManager
имеет ссылку, и клиент хранит ссылку на нее.
Вот пример с weak_ptr
,
std::weak_ptr<SomeObject> weakref = getSomeObject();
// weakref will not keep the object alive if it is removed from the object manager.
auto strongref = weakref.lock();
if ( strongref ) {
// strongref is a shared_ptr and will keep the object alive until it
// goes out of scope.
}
Это может быть полезно в многопоточной среде, так как shared_ptr
Доступ к счетчику ссылок является потокобезопасным. Однако это означает, что клиент может продлить срок службы объекта дольше, чем вам может понравиться.
Если вы хотите использовать интеллектуальные общие указатели, сам вектор должен использовать интеллектуальный указатель.
class SomeObjectManager
{
private:
std::vector<std::shared_ptr<SomeObject> > _heldObjects;
};
Но тогда ты в безопасности.