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
connoexcept
? - Con C ++ 11 y su range-for , ¿debería colocar
friend iter begin()
yfriend iter end()
la misma manera dentro de la clase? Creo que elfriend
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.