web-dev-qa-db-fra.com

Comportement inattendu après affectation de l'objet fonction au wrapper de fonction

Je cherchais un bug dans une application, que j'ai finalement corrigé mais que je ne comprenais pas complètement. Le comportement peut être reproduit avec le programme simple suivant:

#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 sortie de la ligne

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

est 2, ce qui est bien sûr ce que j'attendais.

Mais la sortie de la ligne

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

est 1. Cela m'a surpris. J'ai même été surpris que la mission f = *p est autorisé et ne provoque pas d'erreur.

Je ne demande pas de solution de contournement, car je l'ai corrigé par un lambda.
Ma question est, que se passe-t-il quand je le fais f = *p et pourquoi la sortie 1 plutôt que 2?

J'ai reproduit le problème avec gcc (MinGW) et Visual Studio 2019.
En outre, je tiens à mentionner que la sortie de

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

est 2, encore.

35
Rabbid76

Le découpage d'objets se produit ici.

Le point est donné f = *p;, p est de type std::shared_ptr<Foo>, Puis le type de *p Est Foo& (Au lieu de Bar&). Même l'opérateur d'affectation de std::function prend l'argument par référence, mais

4) Définit la cible de *this Sur le f appelable, comme si en exécutant function(std::forward<F>(f)).swap(*this);.

Notez que le F ci-dessus est également déduit comme Foo&. Et le constructeur de std::function prend l'argument par valeur, le découpage d'objet se produit, l'effet devient que f est assigné à partir d'un objet de type Foo qui est copiée à partir de *p.

template< class F > 
function( F f );
24
songyuanyao

Il s'agit d'un découpage régulier, caché sous une couche de std::function Et std::shared_ptr.

f = *p;

est valide car *p est un objet appelable avec une operator() appropriée, et c'est l'une des choses que vous pouvez encapsuler dans un std::function.

La raison pour laquelle cela ne fonctionne pas est qu'il copie *p - et c'est un Foo&, Pas un Bar&.

Cette adaptation de votre dernier exemple se comporterait de la même manière:

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

Tranchage

Il s'agit d'un tranchage. La raison en est l'opérateur d'affectation de std::function (comme démontré dans un autre réponse également) qui dit:

Définit la cible de * this sur le f appelable, comme si en exécutant la fonction (std :: forward (f)). Swap (* this) ;. Cet opérateur ne participe pas à la résolution de surcharge sauf si f est Callable pour les types d'arguments Args ... et retourne le type R. (depuis C++ 14)

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

Si vous simplifiez et supprimez l'exemple - vous pouvez facilement voir ce qui se passe:

Foo* p =  new Bar;

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

Il semble que vous vouliez obtenir un pointeur sur la fonction virtuelle remplacée - malheureusement, il n'y a pas de moyen facile de dérouler la recherche de fonction virtuelle telle qu'elle est implémentée via une table de recherche d'exécution . Cependant, une solution de contournement simple pourrait être d'utiliser un lambda pour envelopper (comme le OP le mentionne également):

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

Une solution plus appropriée pourrait également consister à simplement utiliser reference_wrapper:

f = std::ref(p);
11
darune

Le type statique du pointeur p est Foo.

Donc, dans cette déclaration

f = *p;

il y a laissé l'opérande *p a le type Foo c'est-à-dire qu'il y a découpage.

0
Vlad from Moscow