Копирует ли boost::function также копирование замыкания?
Скажем, у меня есть такая функция:
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 ответа
Из того, что я могу найти (судя по беглому прочтению не совсем простого источника и некоторым экспериментам), он будет копировать клонированный объект каждый раз. Это может быть ненужным в случае, если функция принимает свой аргумент через const &, но в целом объект может быть видоизменен функцией. Если объект дорогой для копирования, не имеет ли смысла захватывать его по ссылке (boost::ref
или же boost::cref
на ум) или, если исходный объект не существует в момент вызова, запишите boost::shared_ptr
и написать метод адаптера, который распаковывает Smartpointer и вызывает 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 для внутренних вызовов, либо записывается способом, более удобным для inliner gcc, чтобы встроить некоторые из них и устранить избыточные конструкторы копирования. tr1::bind
еще лучше оптимизируется тогда boost::bind
, но все еще далеко от оптимального.
Конечно как всегда с такого рода тестами YMMV с разными компиляторными флагами / компиляторами
Если вы передаете объект по значению 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;
}
выход:
a
after ff
g
g
after called ff()
~a
То есть
- один конструктор вызывается при создании объекта
- при создании объекта функции не вызывается конструктор
ff
или сделать его копию (ff2
) - конструктор не вызывается, когда
g(const a &)
вызывается черезff()
или жеff2()
- деструктор вызывается, когда объект выходит из области видимости