【问题标题】:Delete raw pointer argument to boost::bind删除 boost::bind 的原始指针参数
【发布时间】:2011-05-11 12:03:48
【问题描述】:

假设我已经分配了堆A*,我想将其作为参数传递给boost::bind。 boost::bind 被保存在一些 STL 中,例如 boost::functions 的容器中以供以后处理。

我想确保 A* 在 STL 容器销毁时被销毁。

演示:

A* pA = new A();

// some time later
container.push_back(boost::bind(&SomeClass::HandleA, this, pA);

// some time later
container is destroyed => pA is destroyed too

怎么做?

编辑

也许我想要的不是那么现实。

我有原始指针和接收原始指针的函数。通过 boost::bind 延迟调用。在这一点上,我想要自动内存管理,以防 boost::bind 想要执行。我很懒,所以我想使用“现成的”智能指针解决方案。

std::auto_ptr 看起来不错,但是 ...

auto_ptr<A> pAutoA(pA);
container.push_back(boost::bind(&SomeClass::HandleA, this, pAutoA);

无法编译(参见here)

auto_ptr<A> pAutoA(pA);
container.push_back(boost::bind(&SomeClass::HandleA, this, boost::ref(pAutoA));

pAutoA 被销毁,删除底层 pA。

编辑 02

在提到的容器中,我需要存储带有不同参数的杂项“回调”。其中一些是指向对象的原始指针。由于代码很旧,我并不总是可以更改它。

为在容器中存储回调编写自己的包装器是最后的手段(虽然可能是唯一的),因此是赏金。

【问题讨论】:

    标签: c++ memory-management boost-bind auto-ptr boost-function


    【解决方案1】:

    @pmjordan 的想法已经朝着正确的方向发展。您回答说您不能使用shared_ptr,因为一旦构建,您就无法收回所有权。但这并不完全正确:使用shared_ptr 的自定义删除器机制,您可以。方法如下:

    假设您的A 和f(A*) 有这些玩具定义:

    struct A {
        ~A() { std::cout << "~A()" << std::endl; }
    };
    
    void f( A * a ) {
        std::cout << "in f(A*)" << std::endl;
        delete a;
    }
    
    1. 写一个可以“关闭”的删除器:

      struct opt_delete {
          bool m_delete;
          opt_delete() : m_delete( true ) {}
          template <typename T>
          void operator()( T * t ) {
              if ( m_delete ) delete t;
          }
      };
      
    2. 然后您可以编写一个take() 函数再次获得shared_ptr 有效负载的所有权:

      template <typename T>
      T * take( const boost::shared_ptr<T> & sp ) {
          opt_delete * d = boost::get_deleter<opt_delete>( sp );
          assert( d );
          assert( d->m_delete == true );
          d->m_delete = false;
          return sp.get();
      }
      

      (这会将有效负载留在剩余的 shared_ptr 实例中,但对于您的情况,没关系,assert()s 涵盖了不存在的情况)。

    3. 现在您可以像这样手动包装f(A*):

      void f_sp( const boost::shared_ptr<A> & a ) {
          f( take( a ) );
      }
      
    4. 最后,测试两个场景:

      int main( int argc, char * argv[] ) {
      
          const boost::shared_ptr<A> a( new A, opt_delete() );
      
          const boost::function<void()> func =
              boost::bind( &f_sp, a );
      
          if ( argc >= 2 && *argv[1] == '1' ) // call 'func'
              func();
          else
              ; // don't
      
          return 0;
      }
      

    使用1 参数执行测试程序将打印

    在 f(A*)
    ~A()

    如果没有(或任何其他参数),它将打印

    ~A()

    您可以扩展测试工具以首先将func 放入容器中,但它仍然是安全的。在这种情况下唯一不安全的是多次调用func 副本(但随后您将触发take() 中的第二个断言)。

    编辑:请注意,此机制不是线程安全的。为了使其线程安全,您需要为opt_delete 提供一个互斥锁以使operator() 与take() 同步。

    【讨论】:

    • +1:我喜欢这个答案。我怀疑一个好的解决方案应该使用自定义删除器,并朝这个方向思考;我忘记了 deleter 可以是一个仿函数,因此每个对象都可以有自己的标志。
    【解决方案2】:

    我假设你的意思是你有一些功能,我们称之为f(),它接受一个A*,然后你用boost::bind代理它?您可以更改此功能以改为接受 Boost/TR1 shared_ptr&lt;A&gt; 吗?使用 shared_ptr(或者不太可能的 C++98 std::auto_ptr)应该可以解决您的生命周期问题。

    或者,如果您无法更改f 本身,您可以创建一个接受shared_ptr&lt;A&gt; 的包装器,提取原始指针并使用它调用f。如果你发现自己编写了很多这样的包装器,你可以创建一个模板来生成它们,假设函数签名是相似的。

    【讨论】:

    【解决方案3】:

    注意!这太丑了!

    刚刚草拟了一些概念证明。好吧,据我所知,它可以满足要求-但是这些东西依赖于 const_cast 假设。如果您决定在您的程序中使用类似的东西,请准备好仔细检查您的程序中发生的所有复制结构,并使用 valgrind 来验证没有任何内容被泄露/损坏。

    诀窍在于定义您自己的包装类,它忽略 const 限定符并允许从 const 引用的 auto_ptr 转移 auto_ptr 所有权。如果您尝试复制向量本身,这可能会变得很疯狂。

    因此请务必仔细阅读有关向量复制语义、auto_ptr 所有权转移语义以及最重要的是 - 只需使用 shared_ptr :)

    #include <iostream>
    #include <boost/bind.hpp>
    #include <algorithm>
    #include <vector>
    #include <boost/function.hpp>
    
    class parameter_data
    {
        public:
        ~parameter_data()
        {
            std::cout << "~parameter_data()" << std::endl;
        }
    
        parameter_data()
        {
            std::cout << "parameter_data()" << std::endl;
        }
    };
    
    void f( parameter_data* data )
    {
        std::cout << "Processing data..." << std::endl;
    };
    
    
    class storage_wrapper
    {
        private:
            boost::function<void()> callable;
            std::auto_ptr<parameter_data> data;
        public:
            storage_wrapper( const storage_wrapper& copy ) 
            {
                callable = const_cast< storage_wrapper&>(copy).callable;
                data = const_cast< storage_wrapper&>(copy).data;
            }
    
            storage_wrapper( parameter_data *adata )
                : data( adata )
            {
                callable = boost::bind( &f, adata );
            }
    
            storage_wrapper& operator=( const storage_wrapper& copy)
            {
                callable = const_cast< storage_wrapper&>(copy).callable;
                data = const_cast< storage_wrapper&>(copy).data;
            }
    
            void operator()()
            {
                callable();
            }
    };
    
    int main()
    {
        std::cout << "Start of program" << std::endl;
        {
            std::vector<storage_wrapper> container;
            for ( int i = 0; i < 100; i++ )
                container.push_back( storage_wrapper( new parameter_data() ) );
            for ( int i = 0; i < 100; i++ )
                container[i]();
        }
        std::cout << "End of program" << std::endl;
        return 0;
    }
    

    【讨论】:

    • 这与我之前的情况类似——容器正在存储 storage_wrapper*,而 storage_wrapper d-tor 正在删除参数数据。我尽量避免这种手动解决方案 - 我想避免编写 storage_wrapper 类
    • 请忽略我之前的评论 - 我无法编辑它。这与我之前的情况类似——storage_wrapper 保存了 parameter_data*,并且在其 d-tor 中删除了 parameter_data*,而容器存储了 storage_wrapper*。在容器销毁时,代码正在迭代所有元素并删除它们。您的解决方案具有更强大的内存管理。悬停者,我正在尝试做更多,并且根本没有 storage_wrapper 类。所以代码将只保留本质上的部分——容器、参数数据和以 boost::bind 形式的回调
    • 好吧,如果不编写一些小包装器,我几乎无法想象任何方法 - 你想要的东西违反了一些关于自动所有权操作的安全规则。我怀疑是否存在任何提供 hacky 方式的库解决方案。我的代码有额外的好处,您可以在调用包装器的 operator() 中的 f() 之前释放 auto_ptr 的所有权 - 这将允许 f() 释放内存本身而无需修改 f()。
    【解决方案4】:

    不需要很复杂:

    class MyContainer : public std::vector<boost::function<void ()> > {
    public:
       void push_back(boost::function<void ()> f, A *pA) 
           { push_back(f); vec.push_back(pA); }
       ~MyContainer() 
           { int s=vec.size; for(int i=0;i<s;i++) delete vec[i]; }
    private:
       std::vector<A*> vec;
    };
    

    有一个问题是您需要通过 MyContainer & 而不是 std::vector 引用将其传递给其他函数,否则可以调用原始的 push_back 并且它允许在不提供 A* 指针的情况下可以 push_back 的情况。它也没有检查绑定参数是否是与 pA 相同的 A* 对象。您可以通过更改 push_back 原型来解决这个问题:

    template<class T>
    void push_back(T *object, void (T::*fptr)(), A *pA) 
    {
       push_back(boost::bind(fptr, object, pA)); vec.push_back(pA);
    } 
    

    【讨论】:

    • hmm,解决此解决方案的一些问题的一个想法是使继承私有化。但是,这都是关于包装解决方案的。它仍然不适用于 insert() 函数等,所以这个解决方案有点糟糕,这取决于你在程序中如何使用 std::vector 的接口。
    • 参见 EDIT 02 - 我可以使用不同类型和数量的参数对容器进行回调。比我认为您提出的解决方案不适合。
    • 哦,这有点困难,因为 boost::function 不支持它。你需要像 class I { virtual void *data(int num)const=0; 这样的东西。虚拟 std::string 类型(int num) const=0; };然后使用 std::vector vec;和 typeid(T).name();然后按照 push_back 解决方案中的描述实现它。实现应该有带有适当参数的构造函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多