sorteo secreto secret santa regalos por pistas pikkado para jugar intercambio instrucciones entre amigos amigo c++ c++11 friend copy-and-swap

c++ - secreto - sorteo amigo invisible sin email



función de miembro de intercambio de amigo público (2)

Ese código es equivalente (en casi todos los sentidos) a:

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

Una función amiga definida dentro de una clase es:

  • colocado en el espacio de nombres adjunto
  • automáticamente en inline
  • capaz de referirse a miembros estáticos de la clase sin más calificación

Las reglas exactas están en la sección [class.friend] (Cito los párrafos 6 y 7 del borrador de C ++ 0x):

Una función se puede definir en una declaración de amigo de una clase si y solo si la clase es una clase no local (9.8), el nombre de la función no está calificado y la función tiene un ámbito de espacio de nombres.

Dicha función está implícitamente en línea. Una función amiga definida en una clase está en el alcance (léxico) de la clase en la que está definida. Una función de amigo definida fuera de la clase no lo es.

En la bella respuesta al idioma copy-and-swap-idiom existe un código Necesito un poco de ayuda:

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); } // ... };

y él agrega una nota

Existen otras afirmaciones de que debemos especializar std :: swap para nuestro tipo, proporcionar un intercambio en clase junto con un intercambio libre de funciones, etc. Pero todo esto es innecesario: cualquier uso adecuado del intercambio será a través de una llamada no calificada y nuestra función se encontrará a través de ADL. Una función servirá.

Con friend , estoy un poco en términos "antipáticos", debo admitir. Entonces, mis preguntas principales son:

  • parece una función gratuita , pero está dentro del cuerpo de la clase?
  • ¿Por qué no está este swap estático ? Obviamente, no usa ninguna variable miembro.
  • "¿Cualquier uso adecuado de swap descubrirá el intercambio a través de ADL" ? ADL buscará los espacios de nombres, ¿verdad? ¿Pero también se ve dentro de las clases? ¿O es aquí donde entra un friend ?

Preguntas secundarias:

  • Con C ++ 11, ¿debería marcar mis swap con noexcept ?
  • Con C ++ 11 y su range-for , ¿debería colocar friend iter begin() y friend iter end() la misma manera dentro de la clase? Creo que el friend no es necesario aquí, ¿verdad?

Hay varias formas de escribir swap , algunas mejores que otras. Con el tiempo, sin embargo, se encontró que una única definición funciona mejor. Consideremos cómo podríamos pensar en escribir una función de swap .

Primero vemos que los contenedores como std::vector<> tienen un swap función miembro de un solo argumento, como por ejemplo:

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

Naturalmente, entonces, nuestra clase también debería, ¿verdad? Bueno en realidad no. La biblioteca estándar tiene todo tipo de cosas innecesarias , y un swap miembros es uno de ellos. ¿Por qué? Continúemos.

Lo que debemos hacer es identificar qué es canónico y qué debe hacer nuestra clase para trabajar con él. Y el método canónico de intercambio es con std::swap . Esta es la razón por la cual las funciones de miembro no son útiles: no son la forma en que deberíamos intercambiar cosas, en general, y no influyen en el comportamiento de std::swap .

Entonces, para hacer que std::swap funcione, deberíamos proporcionar (y std::vector<> debería haber proporcionado) una especialización de std::swap , ¿no?

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

Bueno, eso ciertamente funcionaría en este caso, pero tiene un problema evidente: las especializaciones de funciones no pueden ser parciales. Es decir, no podemos especializar clases de plantilla con esto, solo instancias particulares:

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

Este método funciona algunas veces, pero no todo el tiempo. Debe haber una mejor manera.

¡Ahi esta! Podemos usar una función de friend y encontrarla a través de ADL:

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

Cuando queremos cambiar algo, asociamos std::swap y luego hacemos una llamada no calificada:

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é es una función friend ? Hay confusión en esta área.

Antes de que C ++ se estandarizara, las funciones de friend hicieron algo llamado "inyección de nombre de amigo", donde el código se comportó como si la función se hubiera escrito en el espacio de nombres circundante. Por ejemplo, estos fueron equivalentes pre-estándar:

struct foo { friend void bar() { // baz } }; // turned into, pre-standard: struct foo { friend void bar(); }; void bar() { // baz }

Sin embargo, cuando se inventó ADL, esto se eliminó. La función de friend solo podría encontrarse a través de ADL; si lo quería como una función gratuita, debía declararse como tal ( ver esto , por ejemplo). Pero lo! Había un problema.

Si solo usa std::swap(x, y) , su sobrecarga nunca se encontrará, porque ha dicho explícitamente "¡mire en std , y en ninguna otra parte"! Esta es la razón por la que algunas personas sugirieron escribir dos funciones: una como función a encontrar a través de ADL, y la otra para manejar las calificaciones std:: explícitas.

Pero como vimos, esto no puede funcionar en todos los casos, y terminamos con un desastre feo. En cambio, el intercambio idiomático fue por la otra ruta: en lugar de hacer que el trabajo de las clases sea proporcionar std::swap , es tarea de los swappers asegurarse de que no usen swap calificados, como se indicó anteriormente. Y esto tiende a funcionar bastante bien, siempre y cuando la gente lo sepa. Pero ahí reside el problema: ¡no es intuitivo tener que usar una llamada no calificada!

Para hacerlo más fácil, algunas bibliotecas como Boost proporcionaron la función boost::swap , que solo hace una llamada no calificada para swap , con std::swap como un espacio de nombres asociado. Esto ayuda a hacer las cosas concisas de nuevo, pero sigue siendo un fastidio.

Tenga en cuenta que no hay ningún cambio en C ++ 11 en el comportamiento de std::swap , que yo y otros creímos erróneamente que sería el caso. Si fuiste mordido por esto, lee aquí .

En resumen: la función miembro es solo ruido, la especialización es fea e incompleta, pero la función friend está completa y funciona. Y cuando intercambias, usa boost::swap o un swap no calificado con std::swap asociado.

† Informalmente, se asocia un nombre si se considerará durante una llamada de función. Para los detalles, lea §3.4.2. En este caso, std::swap normalmente no se considera; pero podemos asociarlo (agregarlo al conjunto de sobrecargas consideradas por swap no calificado), permitiendo que se encuentre.