c++ object-slicing

c++ - Comportamiento inesperado después de la asignación del objeto de función al contenedor de funciones



object-slicing (4)

Rebanar

Este es un caso de rebanado. La razón es el operador de asignación de std::function (como se demostró en otra answer también) que establece:

Establece el objetivo de * this en la f invocable, como si ejecutara la función (std :: forward (f)). Swap (* this) ;. Este operador no participa en la resolución de sobrecarga a menos que f sea invocable para los tipos de argumento Args ... y devuelva el tipo R. (desde C ++ 14)

https://en.cppreference.com/w/cpp/utility/functional/function/operator%3D

Si simplifica y elimina el ejemplo, puede ver fácilmente lo que está sucediendo:

Foo* p = new Bar; Foo f; f = *p;//<-- slicing here since you deref and then copy the object

Parece que pretendía obtener un puntero a la función virtual anulada; desafortunadamente, no hay una manera fácil de desenrollar la búsqueda de la función virtual, ya que se implementa a través de una tabla de búsqueda en tiempo de ejecución . Sin embargo, una solución fácil podría ser usar una lambda para envolver (como el OP también menciona):

f = [p]{return (*p)();};

Una solución más adecuada también podría ser simplemente usar un reference_wrapper :

f = std::ref(p);

Estaba buscando un error en una aplicación, que finalmente solucioné pero no entendí completamente. El comportamiento se puede reproducir con el siguiente programa simple:

#include <iostream> #include <memory> #include <functional> struct Foo { virtual int operator()(void) { return 1; } }; struct Bar : public Foo { virtual int operator()(void) override { return 2; } }; int main() { std::shared_ptr<Foo> p = std::make_shared<Bar>(); std::cout << (*p)() << std::endl; std::function<int(void)> f; f = *p; std::cout << f() << std::endl; return 0; }

La salida de la línea

std::cout << (*p)() << std::endl;

es 2 , que es como esperaba, por supuesto.

Pero la salida de la línea

std::cout << f() << std::endl;

es 1 . Esto me sorprendió. Incluso me sorprendió que la asignación f = *p esté permitida y no cause un error

No pido una solución, porque la arreglé con una lambda.
Mi pregunta es, ¿qué sucede cuando hago f = *p y por qué la salida es 1 lugar de 2 ?

He reproducido el problema con gcc (MinGW) y Visual Studio 2019.
Además, quiero mencionar que la salida de

Bar b; std::function<int(void)> f1 = b; std::cout << f1() << std::endl;

es 2 , de nuevo.


El corte de objetos ocurre aquí.

El punto se da f = *p; , p es de tipo std::shared_ptr<Foo> , luego el tipo de *p es Foo& (en lugar de Bar& ). Incluso el operador de asignación de std::function toma argumentos por referencia, pero

4) Establece el objetivo de *this en la f invocable, como si ejecutara la function(std::forward<F>(f)).swap(*this); .

Tenga en cuenta que la F anterior se deduce como Foo& también. Y el constructor de std::function toma el argumento por valor, el corte de objetos ocurre, el efecto se convierte en que f se asigna desde un objeto de tipo Foo que se copia en rebanadas de *p .

template< class F > function( F f );


El tipo estático del puntero p es Foo .

Entonces en esta declaración

f = *p;

a la izquierda operando *p tiene el tipo Foo que está allí es el corte.


Esta es una división regular, oculta bajo una capa de std::function y std::shared_ptr .

f = *p;

es válido porque *p es un objeto invocable con un operator() apropiado operator() , y esa es una de las cosas que puede ajustar en una std::function .

La razón por la que no funciona es que copia *p , y eso es un Foo& , no un Bar& .

Esta adaptación de su último ejemplo se comportaría igual:

Bar b; Foo& c = b; std::function<int(void)> f1 = c; std::cout << f1() << std::endl;