删除boost::bind中的原始指针参数

6
假设我有一个堆分配的 A*,我想将其作为参数传递给 boost::bindboost::bind 被保存在类似于 boost::functions 的 STL 容器中以供稍后处理。
我希望确保 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);

无法编译(请参见此处

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

pAutoA已被销毁,正在删除底层的pA。

编辑02

在提到的容器中,我需要存储具有不同参数的各种“回调”。其中一些是指向对象的裸指针。由于代码很旧,我并不总是能够更改它。

编写自己的包装器以在容器中存储回调是最后的手段(也许是唯一的手段),因此会提供悬赏。

4个回答

8
@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. Write a deleter that can be "switched off":

    struct opt_delete {
        bool m_delete;
        opt_delete() : m_delete( true ) {}
        template <typename T>
        void operator()( T * t ) {
            if ( m_delete ) delete t;
        }
    };
    
  2. Then you can write a take() function that takes ownership of the shared_ptr payload again:

    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();
    }
    

    (this will leave the payload in the remaining shared_ptr instances, but for your case, that's ok, and the assert()s cover the cases when it's not).

  3. Now you can manually wrap f(A*) like this:

    void f_sp( const boost::shared_ptr<A> & a ) {
        f( take( a ) );
    }
    
  4. And finally, test the two scenarios:

    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:我喜欢这个答案。我怀疑一个好的解决方案应该使用自定义删除器,并朝着这个方向思考;但我忘了删除器可以是一个函数对象,因此每个对象都可以有自己的标志。 - Alexey Kukanov

3
我猜你的意思是你有一个函数,让我们称之为f(),它接受一个A*,然后你使用boost::bind代理它?你能否更改此函数以接受Boost/TR1 shared_ptr<A>?使用shared_ptr(或者不太可能的C++98 std::auto_ptr)应该可以解决您的生命周期问题。
或者,如果您无法更改f本身,则可以创建一个包装器,它接受一个shared_ptr<A>,提取原始指针并调用f。如果您发现自己编写了很多这些包装器,您可以创建一个模板来生成它们,假设函数签名相似。

我可以更改f(),但无法将A更改为shared_ptr<A*>。我尝试将A更改为auto_ptr<A>,但boost:bind()和auto_ptr不是好朋友http://lists.boost.org/Archives/boost/2002/10/38652.php - 我需要执行boost::bind(boost::ref(auto_ptr_a)),但我无法确保auto_ptr_a 超过boost:bind。 - dimba
通常情况下,我不建议使用auto_ptr<>,除非是在特殊情况下。那么使用shared_ptr<A>有什么问题呢? - pmdj
shared_ptr<A>.reset()会删除A,因为它是对A的唯一引用。我更想释放它,但shared_ptr没有提供*release()*方法。 - dimba
1
我感到困惑。如果 f() 显式释放内存,我不明白为什么还需要自动内存管理。直接绑定原始指针不就可以了吗? - pmdj
你理解得很正确。我想要在STL容器中自动释放由boost::function持有的A*。 - dimba
显示剩余5条评论

1

它不需要非常复杂:

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);
} 

嗯,解决这个问题的一个想法是将继承设为私有。但这样就需要包装解决方案。它仍然无法与insert()函数等一起使用,因此这个解决方案略微不好,具体取决于您在程序中如何使用std::vector的接口。 - tp1
请看EDIT 02 - 我可以将带有不同类型和数量参数的回调函数放入容器中。因此,我认为你提出的解决方案不适用。 - dimba
哦,那有点困难,因为 boost::function 不支持它。你需要像这样的东西:class I { virtual void *data(int num)const=0; virtual std::string type(int num) const=0; }; 然后使用 std::vector<I*> vec; 和 typeid(T).name(); 然后按照 push_back 解决方案中描述的实现它。该实现应具有适当参数的构造函数。 - tp1

1

注意!这很丑陋!

只是简单地实现了一些概念验证。嗯,就我所看到的而言,它做到了要求的事情 - 但这个东西依赖于const_cast的假设。如果您决定在程序中使用类似的东西,请随时准备好仔细检查程序中发生的所有复制构造,并使用valgrind验证没有泄漏/损坏。

诀窍在于定义自己的包装器类,它忽略const限定符并允许从const引用的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 的析构函数正在删除 parameter_data。我尝试避免这样的手动解决方案 - 我想避免编写 storage_wrapper 类。 - dimba
请忽略我之前的评论 - 我无法编辑它。这与我之前的情况类似 - storage_wrapper 持有 parameter_data,在其析构函数中删除 parameter_data,而 container 存储 storage_wrapper*。在 container 销毁时,代码会迭代所有元素并将它们删除。您的解决方案具有更强大的内存管理功能。然而,我正在尝试做得更好,根本没有 storage_wrapper 类。因此,代码仅保留基本部分 - container、parameter_data 和 boost::bind 形式的回调。 - dimba
很难想象不写一些小封装就能实现它 - 您要求的东西会破坏有关自动所有权操作的安全规则。我怀疑是否有任何库解决方案可以提供hacky方法。我的代码有额外的好处,即您可以在调用包装程序的operator()之前释放auto_ptr的所有权,并且这将允许f()自己释放内存而不需要修改f()。 - Mihails Strasuns

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接