НЕТ! Это был бы ужасный API . Да, вы могли бы легко реализовать это в shared_ptr
, но только потому, что вы не могли это сделать, не значит, что вам следует.
Почему это такая плохая идея? Интерфейс на основе простого указателя bar
не сохраняет экземпляр общего указателя. Если bar
где-то сохранит необработанный указатель, а затем завершит работу, нет ничего, что гарантировало бы, что сохраненный указатель не станет висящим в будущем. Единственный способ гарантировать это - сохранить экземпляр общего указателя, а не необработанный указатель (в этом вся суть shared_ptr
!).
Становится хуже: следующий код имеет неопределенное поведение, еслиfoo()
возвращает экземпляр указателя, который имел только одну ссылку при возврате foo()
(например, если foo
- простая фабрика новых объектов):
AnotherClass *ptr = m.foo().get();
// The shared_ptr instance returned by foo() is destroyed at this point
m.bar(ptr); // undefined behavior: ptr is likely a dangling pointer here
Вот параметры;сначала рассмотрите перечисленные ранее, прежде чем рассматривать их преемников.
Если bar(AnotherClass *)
- это внешний API, вам нужно обернуть его безопасным способом, то есть кодом, который вызвал бы Original::bar
должен вызывать MyWrapped::bar
, а оболочка должна делать все необходимое для управления жизненным циклом. Предположим, что есть startUsing(AnotherClass *)
и finishUsing(AnotherClass *)
, и код ожидает, что указатель останется действительным между startUsing
и finishUsing
. Ваша оболочка будет выглядеть так:
class WithUsing {
std::unique_ptr<AnotherClass> owner; /* or shared_ptr if the ownership is shared */
std::shared_ptr<User> user;
public:
WithUsing(std::unique_ptr<AnotherClass> owner, std::Shared_ptr<User> user) :
owner(std::move(owner)), user(std::move(user)) {
user.startUsing(owner.get());
}
void bar() const {
user.bar(owner.get());
}
~WithUsing() {
user.finishUsing(owner.get());
}
};
Затем вы будете использовать WithUsing
в качестве дескриптора объекта User
, и любое использование будет выполняться через этот дескриптор, обеспечивая существование объекта.
Если AnotherClass
копируемое и очень дешевое для копирования (например, состоит из одного или двух указателей), передайте его по значению:
void bar(AnotherClass)
Если реализация bar
не нуждается в изменении значения, может быть определено для получения значения const (объявление может быть без const
, так как оно не 'не имеет значения):
void bar(const AnotherClass a) { ... }
Если bar
не хранит указатель, то не передавайте ему указатель: передайте ссылку на const по умолчанию или не-const ссылка, если необходимо.
void bar(const AnotherClass &a);
void bar_modifies(AnotherClass &a);
Если имеет смысл вызывать bar
с "no object" (он же "null"), тогда:
Если передача AnotherClass
по значению в порядке, используйте std::optional
:
void bar(std::optional<AnotherClass> a);
В противном случае, если AnotherClass
вступает во владение, передает unique_ptr
работает нормально, так как может бе ноль.
В противном случае передача shared_ptr
работает нормально, поскольку может быть нулевой.
Если foo()
создает новый объект (противвозвращая объект, который уже существует), в любом случае он должен возвращать unique_ptr
, не a shared_ptr
. Фабричные функции должны возвращать уникальные указатели: это идиоматический C ++. Иначе вводить в заблуждение, поскольку возврат shared_ptr
предназначен для выражения существующего общего владения .
std::unique_ptr<AnotherClass> foo();
Если bar
должен стать владельцем значения, тогда он должен принимать уникальный указатель - это идиома «Я беру на себя управление временем жизни этого объекта»:
void bar(std::unique_ptr<const AnotherClass> a);
void bar_modifies(std::unique_ptr<AnotherClass> a);
Если bar
должен сохранить общий доступвладение, тогда это должно занять shared_ptr
, и вы будете немедленно конвертировать unique_ptr
, возвращенный из foo()
в общий:
struct MyClass {
std::unique_ptr<AnotherClass> foo();
void bar(std::shared_ptr<const AnotherClass> a);
void bar_modifies(std::shared_ptr<AnotherClass> a);
};
void test() {
MyClass m;
std::shared_ptr<AnotherClass> p{foo()};
m.bar(p);
}
shared_ptr(const Type)
иshared_ptr(Type)
будет делиться собственностью, они обеспечивают постоянное представление и изменяемое представление объекта соответственно. shared_ptr<Foo>
также можно преобразовать в shared_ptr<const Foo>
(но не наоборот, для этого вы должны использовать const_pointer_cast
(с осторожностью). Вы всегда должны по умолчанию использовать объекты как константы и работать только с непостоянными типамикогда в этом есть явная необходимость.
Если метод не изменяет что-либо, сделайте это самодокументированным, приняв вместо него ссылку / указатель на const something
.