【问题标题】:std::shared_ptr which is empty but not nullstd::shared_ptr 为空但不为空
【发布时间】:2020-08-12 07:05:32
【问题描述】:

http://www.cplusplus.com/reference/memory/shared_ptr/

不拥有任何指针的 shared_ptr 称为空 shared_ptr。不指向任何对象的 shared_ptr 称为 null shared_ptr 并且不应被取消引用。请注意,虽然空的 shared_ptr 不一定是空的 shared_ptr,空的 shared_ptr 也不一定是空的 shared_ptr。

我是否能够创建一个空的 std::shared_ptr,即一个不拥有任何东西但不为空(即包含有效负载)的 std::shared_ptr?

用例是将“遗留”代码与现代指针结合起来:鉴于 foo 的调用语法不会更改,

void foo(const MyClass* p) {
   if (p == nullptr) {
       auto defaultObject = std::make_shared<MyClass>("some", "parameters");
       defaultObject->doSomething();
       ...
       defaultObject->doSomethingElse();
       // The default object must be destroyed again
   } else {
       p->doSomething();
       ...
       p->doSomethingElse();
       // p must not be destroyed, its memory is managed somewhere else
   }
}

是否有 doNotOwnButStillPointTo() 的实现允许这样做:

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> wrapper;
    if (p == nullptr) {
        // Create a default object
        wrapper = std::make_shared<MyClass>("some", "parameters");
    } else {
        wrapper = doNotOwnButStillPointTo(p);
    }

    wrapper->doSomething();
    ...
    wrapper->doSomethingElse();
    // p mus not be destroyed, the default object (if created) shall be
}

或者,为了不爱上XY-Problem,是否有不同的智能指针可用或根本没有?

  • 但是,我想补充一点,std::make_shared&lt;MyClass&gt;("some", "parameters") 行实际上是对创建 shared_ptr 的更复杂函数的调用。我想保留这个函数并使用它的结果

【问题讨论】:

  • cplusplus.com 是一个低质量的 C++ 网站。 cppreference.com 好多了。 std::shared_ptr 可以是 empty shared_ptr 可能有一个非空的存储指向,如果使用别名构造函数来创建它。比如来自一个沮丧的人。
  • @PhilLab: '"用例是将“遗留”代码与现代指针结合起来" 为什么不在这里使用unique_ptr?在这种情况下,您的 shared_ptr 永远不会离开您的范围,因此无需共享其所有权语义。

标签: c++ c++11 visual-c++ c++17 smart-pointers


【解决方案1】:

shared_ptr 有一个删除器。这是一个销毁底层对象的多态过程。你可能有一个空的删除器:

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> wrapper;
    if (p == nullptr) {
        // Create a default object
        wrapper = std::make_shared<MyClass>("some", "parameters");
    } else {
        wrapper = std::shared_ptr<MyClass>(p, [](MyClass*){});
    }

    wrapper->doSomething();
    ...
    wrapper->doSomethingElse();
    // p mus not be destroyed, the default object (if created) shall be
}

然而,这会导致糟糕的设计。回到问题XY:目的是什么?有人可能会将对象作为原始指针传递给您(但可能会传递一个 nullptr)。如果提供的是 nullptr 而不是真实对象,您希望创建一个本地对象。您可能希望防止内存泄漏。好的。

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> local;
    if (p == nullptr) {
        // Create a default object
        local = std::make_shared<MyClass>("some", "parameters");
        p = local.get();
    }

    // p is always valid, local will always be destroyed (if exists)
    p->doSomething();
    ...
    p->doSomethingElse();
}

【讨论】:

    【解决方案2】:

    您正在寻找的是shared_ptr 的自定义删除器构造函数。这是为了允许共享指针从其他地方接管指针,当它被销毁时,它将运行您的自定义代码而不是调用删除。在这种情况下,您的自定义代码将什么也不做,因为您不想删除指针。看起来像

    void foo(const MyClass* p) {
        std::shared_ptr<MyClass> wrapper;
        if (p == nullptr) {
            // Create a default object
            wrapper = std::make_shared<MyClass>("some", "parameters");
        } else {
            wrapper = std::shared_ptr(p, [](auto){ });
        }
    
        wrapper->doSomething();
        ...
        wrapper->doSomethingElse();
        // p mus not be destroyed, the default object (if created) shall be
    }
    

    【讨论】:

      【解决方案3】:

      我是否能够创建一个空的 std::shared_ptr,即一个不拥有任何东西但不为空(即包含有效负载)的 std::shared_ptr?

      确实有可能。链接页面正确地说 empty shared_ptr 不一定是 null shared_ptr

      这样的共享指针可以使用构造函数来创建

      template< class Y > 
      shared_ptr(const shared_ptr<Y>&, element_type*);
      

      如果你传递一个空的空共享指针作为第一个参数,非空指针作为第二个参数,那么你会得到一个空的非空共享指针。

      构造函数通常用于指向拥有对象的成员或别名,但它恰好也匹配这种极端情况。特别注意不要让这个共享指针泄漏到这个函数的范围之外。

      在你的情况下:

      wrapper = {std::shared_ptr<void>{}, p};
      

      或者,为了不陷入 XY 问题,是否有不同的智能指针可用或根本没有?

      另一种方法是仅将共享指针用于默认对象的所有权,而是通过p调用函数:

      std::shared_ptr<MyClass> wrapper;
      if (p == nullptr) {
          // Create a default object
          wrapper = std::make_shared<MyClass>("some", "parameters");
          p = wrapper.get();
      }
      
      p->doSomething();
      ...
      p->doSomethingElse();
      

      【讨论】:

      • @NathanOliver 您使用默认构造函数来获取一个空的 null 共享指针。添加了说明。
      【解决方案4】:

      网站 cplusplus.com 对语言的使用往往没有那么严格,特别是相对于标准而言。它更喜欢更多的白话语言,因此可能会造成混淆。

      这尤其与smart_ptr“拥有”某物的含义有关。 From the standard:

      如果shared_­ptr 不拥有指针,则称其为空。

      这与shared_ptr 的“所有权”理念有关。从shared_ptr 的角度来看,如果它是使用将调用删除函数(给定或默认)的指针值创建的,它“拥有”一个指针。任何不以这种方式“拥有”指针的shared_ptr 都是空的。

      但是,这与人类所认为的“所有权”不同。有人可能会说,如果您为 shared_ptr 提供了一个删除器,但实际上并没有删除该对象或以其他方式释放它,那么 shared_ptr 就不会真正 em> 拥有指针。

      但就shared_ptr(及其各种接口)而言,它拥有该指针,即使您给定的删除器不会对它做任何事情。

      至于如何处理你的代码特定的代码,最好使用unique_ptr并有条件地给它一个值来销毁:

      void foo(const MyClass* p) {
         unique_ptr<MyClass> u_p;
         if (p == nullptr) {
             u_p = std::make_unique<MyClass>("some", "parameters");
             p = u_p.get();
         }
      
         p->doSomething();
         ...
         p->doSomethingElse();
         // p must not be destroyed, its memory is managed somewhere else
      }
      

      【讨论】:

        猜你喜欢
        • 2017-06-16
        • 1970-01-01
        • 2019-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多