问题
Are smart pointers considered as pointers? And thus can they implicitly used as pointers?
Let's say I have the following class:
class MyClass {
//...
std::shared_ptr<AnotherClass> foo() { /*whatever*/ };
void bar(AnotherClass* a) { /*whatever too*/ };
//...
}
Then can I use MyClass
the following way?
// m is an instance of MyClass
m.bar(m.foo());
回答1:
NO! It would be a terrible API. Yes, you could easily implement it within shared_ptr
, but just because you could doesn't mean you should.
Why is it such a bad idea? The plain-pointer-based interface of bar
doesn't retain an instance of the shared pointer. If bar
happens to store the raw pointer somewhere and then exit, there's nothing that guarantees that the pointer it had stored won't become dangling in the future. The only way to guarantee that would be to retain an instance of the shared pointer, not the raw pointer (that's the whole point of shared_ptr
!).
It gets worse: the following code is undefined behavior if foo()
returns a pointer instance that had only one reference when foo()
returned (e.g. if foo
is a simple factory of new objects):
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
Here are the options; consider those listed earlier first before considering their successors.
If
bar(AnotherClass *)
is an external API, then you need to wrap it in a safe way, i.e. the code that would have calledOriginal::bar
should be callingMyWrapped::bar
, and the wrapper should do whatever lifetime management is necessary. Suppose that there isstartUsing(AnotherClass *)
andfinishUsing(AnotherClass *)
, and the code expects the pointer to remain valid betweenstartUsing
andfinishUsing
. Your wrapper would be: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()); } };
You would then use
WithUsing
as a handle to theUser
object, and any uses would be done through that handle, ensuring the existence of the object.If
AnotherClass
is copyable and is very cheap to copy (e.g. it consists of a pointer or two), then pass it by value:void bar(AnotherClass)
If the implementation of
bar
doesn't need to change the value, it can be defined to take a const-value (the declaration can be without theconst
as it doesn't matter there):void bar(const AnotherClass a) { ... }
If
bar
doesn't store a pointer, then don't pass it a pointer: pass a const reference by default, or a non-const reference if necessary.void bar(const AnotherClass &a); void bar_modifies(AnotherClass &a);
If it makes sense to invoke
bar
with "no object" (a.k.a. "null"), then:If passing
AnotherClass
by value is OK, then usestd::optional
:void bar(std::optional<AnotherClass> a);
Otherwise, if
AnotherClass
takes ownership, passingunique_ptr
works fine since it can be null.Otherwise, passing
shared_ptr
works fine since it can be null.
If
foo()
creates a new object (vs. returning an object that exists already), it should be returningunique_ptr
anyway, not ashared_ptr
. Factory functions should be returning unique pointers: that's idiomatic C++. Doing otherwise is confusing, since returning ashared_ptr
is meant to express existing shared ownership.std::unique_ptr<AnotherClass> foo();
If
bar
should take ownership of the value, then it should be accepting a unique pointer - that's the idiom for "I'm taking over managing the lifetime of that object":void bar(std::unique_ptr<const AnotherClass> a); void bar_modifies(std::unique_ptr<AnotherClass> a);
If
bar
should retain shared ownership, then it should be takingshared_ptr
, and you will be immediately converting theunique_ptr
returned fromfoo()
to a shared one: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)
and shared_ptr(Type)
will share the ownership,
they provide a constant view and a modifiable view of the object, respectively. shared_ptr<Foo>
is also convertible to shared_ptr<const Foo>
(but not the other way round, you'd use const_pointer_cast
for that (with caution). You should always default to accessing objects as constants, and only working with non-constant types when there's an explicit need for it.
If a method doesn't modify something, make it self-document that fact by having it accept a reference/pointer to const something
instead.
回答2:
No they can't be used interchangable. You would get a compiler error in your example. But you can always get the raw pointer by shared_ptr::get()
.
回答3:
Smart pointers are used to make sure that an object is deleted if it is no longer used (referenced).
Smart pointer are there to manage lifetime of the pointer they own/share.
You can think of a wrapper that has a pointer inside. So the answer is no. However you can access to the pointer they own via get()
method.
Please note that it is not so difficult to make dangling pointers if you use get
method, so if you use it be extra cautious.
来源:https://stackoverflow.com/questions/58308058/can-smart-pointers-be-implicitly-used-as-pointers