Can smart pointers be implicitly used as pointers?

安稳与你 提交于 2021-01-28 20:18:42

问题


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 called Original::bar should be calling MyWrapped::bar, and the wrapper should do whatever lifetime management is necessary. Suppose that there is startUsing(AnotherClass *) and finishUsing(AnotherClass *), and the code expects the pointer to remain valid between startUsing and finishUsing. 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 the User 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 the const 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:

    1. If passing AnotherClass by value is OK, then use std::optional:

      void bar(std::optional<AnotherClass> a);
      
    2. Otherwise, if AnotherClass takes ownership, passing unique_ptr works fine since it can be null.

    3. 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 returning unique_ptr anyway, not a shared_ptr. Factory functions should be returning unique pointers: that's idiomatic C++. Doing otherwise is confusing, since returning a shared_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 taking shared_ptr, and you will be immediately converting the unique_ptr returned from foo() 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!