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 laf
invocable, como si ejecutara lafunction(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;