智能指针可以隐式用作指针吗?

Can smart pointers be implicitly used as pointers?

智能指针算作指针吗?因此它们可以隐式用作指针吗?

假设我有以下 class:

class MyClass {
    //...
    std::shared_ptr<AnotherClass> foo() { /*whatever*/ };
    void bar(AnotherClass* a) { /*whatever too*/ };
    //...
}

那我可以MyClass用下面的方法吗?

// m is an instance of MyClass
m.bar(m.foo());

不,它们不能互换使用。你会在你的例子中得到一个编译器错误。但是你总是可以通过 shared_ptr::get().

获得原始指针

Smart pointers用于确保对象在不再使用(引用)时被删除。

智能指针用于管理指针的生命周期 own/share。

你可以想到一个内部有指针的包装器。所以答案是否定的。但是,您可以通过 get() 方法访问他们拥有的指针。

请注意,使用get方法制作悬挂指针并不难,所以使用时要格外小心。

不!这将是一个 可怕的 API。是的,您可以在 shared_ptr 内轻松实现它,但仅仅因为您可以并不意味着您应该。

为什么这是个坏主意? bar 的基于普通指针的接口不保留共享指针的实例。如果 bar 恰好将原始指针存储在某处然后退出,则无法保证它所存储的指针将来不会变得悬空。保证这一点的唯一方法是保留共享指针的实例,而不是原始指针(这就是 shared_ptr 的全部意义!)。

情况变得更糟:如果 foo() returns 一个指针实例在 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 *),并且代码期望指针在 startUsingfinishUsing 之间保持有效。您的包装器将是:

    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-value(声明可以没有 const 因为它在那里无关紧要):

    void bar(const AnotherClass a) { ... }
    
  • 如果 bar 不存储指针,则不要向其传递指针:默认传递 const 引用,或在必要时传递非 const 引用。

    void bar(const AnotherClass &a);
    void bar_modifies(AnotherClass &a);
    
  • 如果用 "no object" (a.k.a. "null") 调用 bar 是有意义的,那么:

    1. 如果按值传递 AnotherClass 没问题,则使用 std::optional:

      void bar(std::optional<AnotherClass> a);
      
    2. 否则,如果 AnotherClass 获得所有权,则传递 unique_ptr 可以正常工作,因为它可以为 null。

    3. 否则,传递 shared_ptr 可以正常工作,因为它可以为空。

  • 如果 foo() 创建一个新对象(相对于返回一个已经存在的对象),它应该返回 unique_ptr 不是 一个shared_ptr。工厂函数应该返回唯一的指针:这是惯用的 C++。否则会造成混淆,因为返回 shared_ptr 是为了表达现有的共享所有权 .

    std::unique_ptr<AnotherClass> foo();
    
  • 如果 bar 应该获得该值的所有权,那么它应该接受一个唯一的指针 - 这是 "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);
    
  • 如果 bar 应该保留共享所有权,那么它应该占用 shared_ptr,您将立即转换从 [=23= 返回的 unique_ptr ] 共享一个:

    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(谨慎)。你应该始终默认访问对象作为常量,并且只在明确需要时使用非常量类型。

如果一个方法没有修改某些东西,让它通过接受 reference/pointer 到 const something 来自我记录这个事实。