【问题标题】:Getting shared_ptr to call a member function once its reference count reaches 0让 shared_ptr 在其引用计数达到 0 时调用成员函数
【发布时间】:2010-06-20 11:48:19
【问题描述】:

我正在为一个不能与 DuplicateHandle 一起使用的 HANDLE 创建一个包装器,因此我尝试将句柄包装在一个 shared_ptr 中。

想象一下下面的代码:

class CWrapper
{
public:
    CWrapper() :
        m_pHandle(new HANDLE, &CWrapper::Close)
    {
        //code to open handle
    }

private:
    void Close() 
    { 
        //code to close handle
    }

    std::shared_ptr<HANDLE> m_pHandle;
}

我也尝试使用 HANDLE 参数创建关闭(不理想)。无论哪种方式,我都会收到编译器错误“术语不会评估为采用 0 个参数的函数”。这是因为隐含的 this 指针吗?我该如何解决?如何从共享指针调用成员函数?

【问题讨论】:

    标签: c++ shared-ptr raii


    【解决方案1】:

    您不能调用成员函数,但可以使用自定义删除器,它是一个全局函数,如下所示:

    void my_deleter(Foo *ptr)
    {
     delete ptr;
     std::cout<< "using custom deleter" <<std::endl;
    }
    shared_ptr<Foo> pf (new Foo, my_deleter); 
    

    【讨论】:

      【解决方案2】:

      我认为你的抽象方法是错误的。

      shared_ptr 为您提供了一个共享资源的可复制“句柄”,而该共享资源本身不能被复制。将 shared_ptr 与在删除时不执行自身清理的类型一起使用并不是最佳用途。

      如果让你的类在其析构函数中正确地清理这个固有的不可复制资源的单一职责,那么你可以使用shared_ptr 来提供共享所有权,这是它的单一职责应该是什么。 (我认为 HANDLE 是不可复制的,就像您尝试制作 HANDLE 的简单副本一样,这些副本不能被视为独立的;最后一个副本必须正确关闭,以便副本的所有者需要了解其他副本。)

      class CWrapper
      {
      public:
          CWrapper()
          {
              // code to open handle
          }
      
          ~CWrapper()
          {
              // code to close handle
          }
      
      private:
          // prevent copying
          CWrapper(const CWrapper&);
          CWrapper& operator=(const CWrapper&);
      
          HANDLE mHandle;
      };
      

      现在在需要共享句柄的地方使用shared_ptr&lt;CWrapper&gt;,如果您认为这太冗长,可以使用 typedef。

      自定义删除器是一个过于复杂的解决方案,恕我直言。

      【讨论】:

      • 我认为你是对的,我的抽象是错误的。我一直认为阻止对象的复制是不自然的,但是现在使用您的 shared_ptr 技术,我意识到我只需要阻止此类的复制!谢谢。
      【解决方案3】:

      如果您需要从Close 中访问非静态成员,您需要正确绑定其this 参数

      CWrapper() :
         m_pHandle(new HANDLE, boost::bind(&CWrapper::Close, this, _1)) {
          //code to open handle
      }
      

      然而,这包含一个隐藏的错误。您的对象是可复制的,并且您将删除器绑定到 *this 的对象。句柄与您创建的第一个包装器相关联,但如果您复制包装器,则该句柄是共享的,但与第一个包装器相关联,可能不再存在:

      CWrapper getWrapper() { CWrapper w; return w;  }
      CWrapper x = getWrapper();
      

      在执行该代码并且 x 将被销毁后,行为未定义,因为 x 对内部句柄指针的销毁将尝试使用绑定在 w 的构造函数调用中的对象 - 然而那个对象已经不存在了!

      对此的解决方案是将与句柄关联的数据存储在分配的对象本身中,而不是尝试将其存储在顶级句柄对象中,如以下代码中所示

      class CWrapper
      {
      public:
        CWrapper():m_pHandle(new CHandle)
        { }
      
      private:
          // This class cannot be copied
          class CHandle : boost::noncopyable {
            friend class CWrapper;
      
            CHandle() 
              :m_pHandle(new HANDLE) {
                // code to open handle
            }
      
            ~CHandle() {
              // code to close this handle, making use of 
              // auxilary data for whatever reason
            }
      
          private:
            boost::scoped_ptr<HANDLE> m_pHandle;
            // auxilary data associated with the handle...
          };
      
          boost::shared_ptr<CHandle> m_pHandle;
      };
      

      辅助数据不再存储在句柄中,而是与包装器的所有副本共享的数据一起存储。共享数据本身是使用其正常的构造函数和析构函数创建和销毁的。

      CWrapper getHandle() { return myHandle; }
      CWrapper w = getHandle();
      

      最后一个过期的包装器将破坏句柄,该句柄在所有包装器之间显式共享。

      【讨论】:

      • 这是一个非常漂亮的解决方案,也是一种非常有趣的资源管理思维方式。谢谢您的回答!并感谢有关此错误的提示。我几乎使用了 lamda 解决方案,并没有考虑就使用它。
      • 我知道这个答案似乎已经过时了,但是感谢您指出我过去几天一直在寻找的这个隐藏的错误! +1
      • 正是我需要的,虽然我使用了现在可用的std::bind
      【解决方案4】:

      我没有测试过,但是根据shoosh提出的想法,你或许可以像这样传递一个成员函数:

      void Class::myDeleteMember(Foo *ptr)
      {
       delete ptr;
       std::cout<< "using custom deleter" <<std::endl;
      }
      shared_ptr<Foo> pf (new Foo, boost::bind(&Class::myDeleteMember, _1)); 
      

      【讨论】:

      • 他似乎在使用 tr1,因为他有 std::shared_ptr 而不是 boost::shared_ptrbind() 在 tr1 中吗?
      • 我对tr1不是很熟悉,但是从我根据en.wikipedia.org/wiki/…收集到的信息来看,不是吗?
      • 我也玩过 C++0x 方式:shared_ptr ph (new HANDLE, [this](HANDLE *){ Close(); });
      • 如果你有 C++0x,那应该是最好的方法。但是,您确定您得到的是 HANDLE* 而不是 HANDLE&?
      • 它也不能幸免于 Johannes 描述的隐藏错误。
      【解决方案5】:

      在 boost::shared_ptr 文档中查找 deleter。我找不到它的直接链接,但基本上它是一个在 ref 为 0 时调用的函子。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-03-30
        • 1970-01-01
        • 2011-11-29
        • 1970-01-01
        • 1970-01-01
        • 2020-12-04
        • 2018-11-24
        • 1970-01-01
        相关资源
        最近更新 更多