Копирует ли boost :: function также копирование замыкания? - PullRequest
5 голосов
/ 31 декабря 2011

Скажем, у меня есть такая функция:

void someFunction(const ExpensiveObjectToCopy&);

Если я сделаю boost :: function, то она сохранит свою собственную клонированную копию объекта в своем закрытии:

boost::function<void()> f = boost::bind(someFunction, x);  // <-- f saves a copy of x

Теперь, если я начну передавать f, будет ли копирующий конструктор boost :: function каждый раз снова копировать этот объект, или каждая функция будет иметь одно и то же замыкание? (то есть так)

boost::function<void()> f2 = f;
callSomeFunction(f);
etc.

Ответы [ 2 ]

4 голосов
/ 31 декабря 2011

Из того, что я могу найти (судя по беглому прочтению не совсем простого источника и некоторым экспериментам), оно будет каждый раз копировать клонированный объект. Это может быть ненужным в случае, если функция принимает аргумент через const &, но в целом объект может быть видоизменен функцией. Если объект дорогой для копирования, не имеет ли смысла захватывать его по ссылке (на ум приходит boost::ref или boost::cref) или, если исходный объект не существует в момент вызова, захватить boost::shared_ptr и написать метод адаптера, который распаковывает смартпоинтер и вызывает someFunction?

Редактировать: Из эксперимента он не только копирует конструкцию этого объекта всякий раз, когда копируется boost::function, но также копирует несколько раз внутри boost::bind. Я тестировал, используя следующий код, используя boost 1.45 под mingw 32 с gcc 4.6 и -O2 (и -std = c ++ 0x):

struct foo_bar {
    std::vector<int> data; //possibly expensive to copy
    foo_bar()
    { std::cout<<"default foo_bar "<<std::endl; }
    foo_bar(const foo_bar& b):data(b.data)
    {  std::cout<<"copy foo_bar "<<&b<<" to "<<this<<std::endl; }
    foo_bar& operator=(const foo_bar& b) {
        this->data = b.data;
        std::cout<<"asign foo_bar "<<&b<<" to "<<this<<std::endl;
        return *this;
    }
    ~foo_bar(){}
};

void func(const foo_bar& bar) { std::cout<<"func"<<std::endl;}

int main(int, char*[]) {
    foo_bar fb;
    boost::function<void()> f1(boost::bind(func, fb));
    std::cout<<"Bind finished"<<std::endl;
    boost::function<void()> f2(f1);
    std::cout<<"copy finished"<<std::endl;
    f1();
    f2();
    return 0;
}

Полученный результат выглядит следующим образом:

default foo_bar
copy foo_bar 0x28ff00 to 0x28ff10
copy foo_bar 0x28ff10 to 0x28ff28
copy foo_bar 0x28ff28 to 0x28ff1c
copy foo_bar 0x28ff1c to 0x28ff34
copy foo_bar 0x28ff34 to 0x28fed4
copy foo_bar 0x28fed4 to 0x28fee4
copy foo_bar 0x28fee4 to 0x28fef4
copy foo_bar 0x28fef4 to 0x28fe14
copy foo_bar 0x28fe14 to 0x28fe24
copy foo_bar 0x28fe24 to 0x28fe34
copy foo_bar 0x28fe34 to 0x6a2c7c
Bind finished
copy foo_bar 0x6a2c7c to 0x6a2c94
copy finished
func
func

Таким образом, конструктор копирования был вызван для создания f2 один раз и 11 раз для привязки и присвоения f1. Так как первый объект создается в стеке, а адреса копий очень близки к этому и немного увеличиваются, кажется, что процесс связывания проходит через множество функций, которые компилятор не вставляет в этом случае и которые каждый передать объект по значению. Использование только boost::bind без сохранения результата в любом месте:

int main(int, char*[]) {
    foo_bar fb;
    boost::function<void()> f1(boost::bind(func, fb));
    return 0;
}

default foo_bar
copy foo_bar 0x28ff00 to 0x28ff10
copy foo_bar 0x28ff10 to 0x28ff28
copy foo_bar 0x28ff28 to 0x28ff1c
copy foo_bar 0x28ff1c to 0x28ff34
copy foo_bar 0x28ff34 to 0x28fef4

Итак, пять копий просто для привязки объекта. Поэтому я бы в целом избегал захвата всего, что имеет как минимум умеренную стоимость копирования на единицу, в любых, даже отдаленно чувствительных к производительности частях кода. В сравнении gccs std::tr1::bind и std::bind работают намного лучше (в сочетании с std :: tr1 :: function / std :: function) (код в основном идентичен первому тестовому коду, просто замените boost:: на std::tr1:: соответственно std::

std::tr1::bind with std::tr1::function:
default foo_bar
copy foo_bar 0x28ff10 to 0x28ff28
copy foo_bar 0x28ff28 to 0x28ff34
copy foo_bar 0x28ff34 to 0x28ff04
copy foo_bar 0x28ff04 to 0x652c7c
Bind finished
copy foo_bar 0x652c7c to 0x652c94
copy finished
func
func

std::bind with std::function:
default foo_bar
copy foo_bar 0x28ff34 to 0x28ff28
copy foo_bar 0x28ff28 to 0x3c2c7c
Bind finished
copy foo_bar 0x3c2c7c to 0x3c2c94
copy finished
func
func

Я предполагаю, что std::bind либо проходит по const ref для внутренних вызовов, либо написано так, что более удобно для inlineer gccs встроить некоторые в и устранить избыточные конструкторы копирования. tr1::bind все еще лучше оптимизируется, чем boost::bind, но все еще далеко от оптимального.

Конечно, как всегда, при такого рода тестах YMMV с разными флагами компиляции / компиляторами

3 голосов
/ 31 декабря 2011

Если вы передаете объект по значению bind, он копируется (как вы тестировали: 11 раз).

Но , если вы не хотите делать копию, передайте ее по ссылке (используя boost::cref), и она не будет скопирована.

struct a
{
    a() { std::cout << __func__ << std::endl; }
    a(const a &) { std::cout << __func__ << std::endl; }
    ~a() { std::cout << __func__ << std::endl; }
    const a & operator=(const a & aa)
    { std::cout << __func__ << std::endl; return aa; }
};

void g(const a &) { std::cout << __func__ << std::endl; }


void t2()
{
    a aa;

    boost::function< void() > ff = boost::bind(g, boost::cref(aa));
    boost::function< void() > ff2 = ff;
    std::cout << "after ff" << std::endl;
    ff();
    ff2();
    std::cout << "after called ff()" << std::endl;
}

выход: * +1010 *

a
after ff
g
g
after called ff()
~a

То есть

  1. один конструктор вызывается при создании объекта
  2. конструктор не вызывается при создании объекта функции ff или его копировании (ff2)
  3. конструктор не вызывается, когда g(const a &) вызывается через ff() или ff2()
  4. деструктор вызывается, когда объект выходит из области видимости
...