2011-12-31 10 views
5

Digamos que tiene una función como esta:¿Copia una función boost :: también copia el cierre?

void someFunction(const ExpensiveObjectToCopy&); 

Si hago un impulso :: función a cabo si, esa función almacenará su propia copia clonada del objeto en su cierre:

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

Ahora, si empiezo a pasar f, ¿el constructor de copia boost :: function copiará ese objeto nuevamente cada vez, o cada función compartirá el mismo cierre? (Es decir, como esto)

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

Respuesta

4

De lo que puedo encontrar (a juzgar por una lectura superficial de la fuente no es precisamente sencillo y algunos experimentos) se copia el objeto clonado cada vez. Puede ser innecesario en caso de que la función tome su argumento mediante const &, pero en general el objeto puede ser mutado por la función. Si el objeto es caro de copiar, ¿no tendría sentido capturarlo por referencia (boost::ref o boost::cref viene a la mente) o, si el objeto original no existe en el punto de invocación, capturar un boost::shared_ptr y escribir un adaptador método, que desempaqueta el smartpointer y llama al someFunction?

Editar: A partir de la experimentación no solo se copiará construir ese objeto siempre que se copie boost::function, sino que también se copiará varias veces dentro de boost::bind. Probé usando el siguiente código usando impulso 1.45 bajo MinGW 32 con gcc 4.6 y -O2 (y -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; 
} 

La salida resultante es como sigue:

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 

Entonces se solicitó al constructor de copias que creara f2 una vez y 11 veces para el enlace y la asignación a f1. Dado que el primer objeto se crea en la pila y las direcciones de las copias están muy cerca de eso y aumentando ligeramente, parece que el proceso de enlace pasa por muchas funciones, que el compilador no alinea en este caso y que cada una pasar el objeto por valor Utilizando sólo boost::bind sin guardar el resultado en cualquier lugar:

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 

Así cinco copias sólo para vincular el objeto. Así que, en general, evitaría capturar cualquier cosa que tenga al menos costos de copia moderados por valor en cualquier parte del código que sea remotamente sensible al rendimiento. En GCCS comparación std::tr1::bind y std::bind realizar mucho mejor (en conjunción con std :: TR1 :: función/std :: función) (código es básicamente idéntica a la primera testcode, simplemente sustituya boost:: con std::tr1:: respectivamente 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 

Asumo que std::bind pasa por const ref para invocaciones internas, o está escrito de una manera que es más amigable con gccs inline para alinear algunos y eliminar constructores de copia redundantes. tr1::bind aún se optimiza mejor que boost::bind, pero aún está lejos de ser óptimo.

Por supuesto como siempre con tal tipo de pruebas YMMV con diferentes banderas de compilación/compiladores

+0

escribí un programa de prueba y parece que el constructor copia del objeto almacenado se consiga llamar cada vez que se copia el objeto de función . Además, boost :: bind llama al constructor de copia 11 veces. – Chris

+0

@Chris: Ok, entonces mi prueba no fue una casualidad, es bueno saberlo. Entonces, si uno puede usar C++ 11, parece que std :: bind es el camino a seguir por un margen lejano (aunque personalmente yo solo usaría lambdas en su lugar). – Grizzly

3

Si pasa objeto por valor a bind se copia (como lo probó: 11 veces).

Pero si no desea copiar y luego pasarlo por referencia (usando boost::cref) y no se copia.

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

de salida:

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

Eso es

  1. un constructor llamado cuando el objeto creado
  2. ningún constructor llama al crear la función objeto ff o hacer una copia de la misma (ff2)
  3. sin constructor llama cuando g(const a &) llama a través de ff() o ff2()
  4. destructor llamada cuando el objeto sale del ámbito
Cuestiones relacionadas