Удалите необработанный аргумент указателя для boost :: bind - PullRequest
6 голосов
/ 11 мая 2011

Допустим, у меня выделена куча 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

Как это можно сделать?

EDIT

Может быть, то, что я хочу, не так реалистично.

У меня есть необработанный указатель и функция, которая получает необработанный указатель. Вызов задерживается с помощью 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 голосов
/ 15 мая 2011

Идея @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() охватывает случаи, когда это не так).

  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 выведет

в ф (A *)
~ А () * +1044 *

и без (или любого другого аргумента) будет напечатано

* * ~ Тысяча сорок-девять А ()

Вы можете расширить тестовый жгут, чтобы сначала поместить func в контейнер, но он все равно будет безопасным. Единственное, что в данном случае небезопасно, - это вызов func копий более одного раза (но тогда вы вызовете второе утверждение в take()).

EDIT : обратите внимание, что этот механизм не является потокобезопасным. Чтобы сделать потокобезопасным, вам нужно предоставить opt_delete мьютекс для синхронизации operator() с take().

3 голосов
/ 11 мая 2011

Полагаю, вы имеете в виду, что у вас есть какая-то функция, давайте назовем ее f(), которая принимает A*, которую вы затем проксируете с boost::bind?Можете ли вы изменить эту функцию, чтобы вместо нее принимать Boost / TR1 shared_ptr<A>?Использование shared_ptr (или, что менее вероятно, C ++ 98 std::auto_ptr) должно решить вашу проблему жизненного цикла.

В качестве альтернативы, если вы не можете изменить f, вы можете создать оболочкукоторый принимает shared_ptr<A>, вытаскивает необработанный указатель и вызывает f с ним.Если вы обнаружите, что пишете много этих оболочек, вы можете создать шаблон для их генерации, предполагая, что сигнатуры функций похожи.

1 голос
/ 15 мая 2011

Это не должно быть очень сложным:

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, и это допускает случаи, когда вы можете push_back без предоставления указателя A *. Кроме того, он не проверяет, чтобы параметры связывания были тем же объектом A *, что и pA. Вы можете исправить это, изменив прототип 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);
} 
1 голос
/ 11 мая 2011

NB! Это ужасно!

Только что вычеркнул некоторые доказательства концепции. Ну, насколько я могу судить, он выполняет то, что запрашивал, но этот материал основан на предположении const_cast. Если вы решите использовать что-то подобное в своей программе, будьте готовы дважды проверить все конструкции копирования, происходящие в вашей программе все время, и с помощью valgrind убедиться, что ничего не пропущено / не повреждено.

Хитрость в том, чтобы определить собственный класс-обертку, который игнорирует квалификаторы const и позволяет передавать владение auto_ptr из auto_ptr, на который ссылается const. Это может сойти с ума, если вы попытаетесь, например, скопировать сам вектор.

Обязательно внимательно прочитайте семантику векторного копирования, семантику передачи прав собственности 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;
}
...