210 votes

Fonction membre publique d'échange d'ami

Dans la belle réponse à la copie et échange d'idiome il y a un morceau de code pour lequel j'ai besoin d'un peu d'aide :

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

et il ajoute une note

Il y a d'autres revendications selon lesquelles nous devrions spécialiser std::swap pour notre type, fournir un swap de classe en plus d'un swap de fonction libre, etc. Mais tout cela n'est pas nécessaire : toute utilisation correcte de swap se fera par un appel non qualifié, et notre fonction sera trouvée par ADL. Une fonction suffira.

Avec friend Je dois admettre que je suis un peu " inamical ". Donc, mes principales questions sont :

  • semble être une fonction gratuite mais il est à l'intérieur du corps de la classe ?
  • pourquoi ce n'est pas swap statique ? Il est évident qu'il n'utilise pas de variables membres.
  • "Toute utilisation correcte de swap trouvera swap via ADL" ? ADL va rechercher les espaces de noms, n'est-ce pas ? Mais regarde-t-il aussi à l'intérieur des classes ? Ou est-ce ici que friend entre en jeu ?

Questions secondaires :

  • Avec C++11, dois-je marquer mes swap s avec noexcept ?
  • Avec C++11 et son gamme pour dois-je placer friend iter begin() et friend iter end() de la même manière à l'intérieur de la classe ? Je pense que le friend n'est pas nécessaire ici, n'est-ce pas ?

0 votes

Si l'on considère la question secondaire sur le for basé sur l'intervalle, il est préférable d'écrire des fonctions membres et de laisser l'accès à l'intervalle sur begin() et end() dans l'espace de noms std (§24.6.5), le for basé sur l'intervalle les utilise en interne depuis l'espace de noms global ou std (voir §6.5.4). Cependant, il vient avec un inconvénient que ces fonctions font partie de l'en-tête <iterator>, si vous ne l'incluez pas, vous pourriez vouloir les écrire vous-même.

8 votes

Pourquoi n'est-il pas statique - parce qu'un friend n'est pas du tout une fonction membre.

211voto

GManNickG Points 155079

Il existe plusieurs façons d'écrire swap certains meilleurs que d'autres. Avec le temps, cependant, on a constaté qu'une seule définition était la plus efficace. Réfléchissons à la manière dont nous pourrions envisager l'écriture d'une swap función.


Nous voyons d'abord que les conteneurs comme std::vector<> ont une fonction membre à argument unique swap comme :

struct vector
{
    void swap(vector&) { /* swap members */ }
};

Naturellement, notre classe devrait en faire autant, non ? Eh bien, pas vraiment. La bibliothèque standard a toutes sortes de choses inutiles et un membre swap est l'un d'entre eux. Pourquoi ? Continuons.


Ce que nous devrions faire est d'identifier ce qui est canonique, et ce que notre classe besoins à faire pour travailler avec lui. Et la méthode canonique d'échange est avec std::swap . C'est la raison pour laquelle les fonctions membres ne sont pas utiles : elles ne correspondent pas à la manière dont nous devrions échanger les choses, en général, et n'ont aucune incidence sur le comportement de std::swap .

Bien alors, pour faire std::swap travail que nous devons fournir (et std::vector<> aurait dû fournir) une spécialisation de std::swap n'est-ce pas ?

namespace std
{
    template <> // important! specialization in std is OK, overloading is UB
    void swap(myclass&, myclass&)
    {
        // swap
    }
}

Cela fonctionnerait certainement dans ce cas, mais il y a un problème flagrant : les spécialisations de fonctions ne peuvent pas être partielles. En d'autres termes, nous ne pouvons pas spécialiser les classes de modèles avec ceci, seulement des instanciations particulières :

namespace std
{
    template <typename T>
    void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
    {
        // swap
    }
}

Cette méthode fonctionne parfois, mais pas toujours. Il doit y avoir un meilleur moyen.


C'est le cas ! Nous pouvons utiliser un friend et le trouver grâce à la fonction ADL :

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}

Quand nous voulons échanger quelque chose, nous associons † std::swap et ensuite faire un appel non qualifié :

using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first

// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap

Qu'est-ce qu'un friend fonction ? Il existe une certaine confusion dans ce domaine.

Avant que le C++ ne soit standardisé, friend les fonctions ont fait quelque chose appelé "injection de nom d'ami", où le code se comporte comme si si la fonction avait été écrite dans l'espace de nom environnant. Par exemple, il s'agissait d'équivalents pré-standard :

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

Cependant, lorsque ADL a été inventé, cela a été supprimé. Le site friend pourrait alors seulement être trouvée via ADL ; si vous la vouliez comme une fonction libre, elle devait être déclarée comme telle ( voir ceci par exemple). Mais voilà, il y avait un problème.

Si vous utilisez seulement std::swap(x, y) votre surcharge sera jamais être trouvé, parce que vous avez explicitement dit "regarder dans std et nulle part ailleurs" ! C'est pourquoi certaines personnes ont suggéré d'écrire deux fonctions : l'une comme une fonction à trouver via ADL et l'autre pour traiter les données explicites std:: qualifications.

Mais comme nous l'avons vu, cela ne peut pas fonctionner dans tous les cas, et nous nous retrouvons avec un vilain désordre. Au lieu de cela, l'échange idiomatique a suivi l'autre voie : au lieu de faire en sorte que les classes fournissent std::swap c'est le travail des échangeurs de s'assurer qu'ils n'utilisent pas de produits qualifiés. swap comme ci-dessus. Et cela a tendance à fonctionner assez bien, tant que les gens le savent. Mais c'est là que réside le problème : il n'est pas intuitif de devoir utiliser un appel non qualifié !

Pour rendre cela plus facile, certaines bibliothèques comme Boost ont fourni la fonction boost::swap qui se contente de faire un appel non qualifié à swap con std::swap comme espace de nom associé. Cela permet de rendre les choses plus succinctes, mais c'est quand même dommage.

Notez qu'il n'y a pas de changement dans C++11 au comportement de std::swap ce que j'ai cru à tort, comme d'autres, être le cas. Si vous avez été mordu par ça, lire ici .


En résumé, la fonction membre n'est que du bruit, la spécialisation est laide et incomplète, mais la friend La fonction est complète et fonctionne. Et lorsque vous échangez, utilisez soit boost::swap ou un swap con std::swap associé.


†Informellement, un nom est associé à s'il sera pris en compte lors d'un appel de fonction. Pour les détails, lisez le §3.4.2. Dans ce cas, std::swap n'est normalement pas prise en compte, mais nous pouvons associé (l'ajouter à l'ensemble des surcharges considérées par les personnes non qualifiées). swap ), ce qui permet de le trouver.

0 votes

Encore une excellente réponse :) Peut-être ajouter exactement ce que vous voulez dire avec with std::swap` associé` ?

0 votes

Une simple demande : pouvez-vous fournir la référence de la partie du projet C++11 indiquant que std::swap doit d'abord utiliser ADL-found swap ? Je n'arrive pas à la trouver (la seule partie pertinente est le §17.6.3.2, mais elle ne concerne que la bibliothèque standard).

0 votes

@GMan : Oui, je suis au courant de cela. Mais si l'on considère que std::swap dans <utility> (§20.2.2) ne mentionne pas le concept de Swappable, il ne semble pas que toute utilisation générale de std::swap soit garantie d'utiliser un swap adapté trouvé par ADL.

8voto

Ben Voigt Points 151460

Ce code est équivalent (en presque de toutes les façons) à :

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

Une fonction amie définie à l'intérieur d'une classe est :

  • placé dans l'espace de nom englobant
  • automatiquement inline
  • capable de faire référence à des membres statiques de la classe sans autre qualification

Les règles exactes sont dans la section [class.friend] (Je cite les paragraphes 6 et 7 du projet C++0x) :

Une fonction peut être définie dans une déclaration d'ami d'une classe si et seulement si la classe est une classe non locale (9.8), le nom de la fonction n'est pas qualifié et la fonction a la portée d'un espace de nom.

Une telle fonction est implicitement inline. Une fonction amie définie dans une classe est dans la portée (lexicale) de la classe dans laquelle elle est définie. Une fonction amie définie en dehors de la classe ne l'est pas.

3 votes

En fait, les fonctions amies ne sont pas placées dans l'espace de nom englobant, en C++ standard. L'ancien comportement était appelé "injection de nom d'ami", mais il a été supplanté par ADL, remplacé dans la première norme. Voir le haut de ce . (Le comportement est assez similaire, cependant).

0 votes

@GMan : Est-ce que cela a changé entre C++03 et C++0x ?

0 votes

L'inline est clair. Ok, il y a une autre réponse qui m'aide ici.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X