En la hermosa respuesta al idioma de copiar e intercambiar hay un código que 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 agrega una nota
Hay otras afirmaciones de que deberíamos especializarnos en std :: swap para nuestro tipo, proporcionar un intercambio en clase junto con un intercambio de función libre, etc. Pero esto es innecesario: cualquier uso adecuado de swap se realizará 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
que estoy un poco en términos "hostiles", debo admitirlo. Entonces, mis preguntas principales son:
- parece una función libre , pero está dentro del cuerpo de la clase?
- ¿Por qué no es esto
swap
estático ? Obviamente no usa ninguna variable miembro. - "¿Cualquier uso apropiado de swap lo descubrirá 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
friend
entra?
Preguntas secundarias:
- Con C ++ 11, ¿debo marcar mi
swap
s connoexcept
? - Con C ++ 11 y su rango-para , debo colocar
friend iter begin()
yfriend iter end()
de la misma manera dentro de la clase? Creo quefriend
no se necesita aquí, ¿verdad?
c++
c++11
friend
copy-and-swap
Towi
fuente
fuente
friend
función no es una función miembro en absoluto.Respuestas:
Hay varias formas de escribir
swap
, algunas mejores que otras. Sin embargo, con el tiempo, se descubrió que una sola definición funciona mejor. Consideremos cómo podríamos pensar en escribir unaswap
función.Primero vemos que los contenedores como
std::vector<>
tienen una función miembro de argumento únicoswap
, como: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 miembro
swap
es una de ellas. ¿Por qué? Continúemos.Lo que debemos hacer es identificar qué es canónico y qué necesita 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 miembro no son útiles: no son cómo debemos intercambiar cosas, en general, y no influyen en el comportamiento destd::swap
.Bueno, para hacer el
std::swap
trabajo deberíamos proporcionar (ystd::vector<>
deberíamos haber proporcionado) una especializaciónstd::swap
, ¿verdad?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 plantillas con esto, solo instancias particulares:
Este método funciona algunas veces, pero no todo el tiempo. Debe haber una mejor manera.
¡Ahi esta! Podemos usar una
friend
función y encontrarla a través de ADL :Cuando queremos intercambiar algo, asociamos †
std::swap
y luego hacemos una llamada no calificada:¿Qué es una
friend
función? Hay confusión en esta área.Antes de que C ++ se estandarizara, las
friend
funciones 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 pre-estándar equivalentes:Sin embargo, cuando se inventó ADL, esto se eliminó. La
friend
función solo se puede encontrar a través de ADL; si lo deseaba como una función libre, debía declararse así ( consulte esto , por ejemplo). Pero he aquí! Había un problema.Si solo usa
std::swap(x, y)
, su sobrecarga nunca será encontrada, porque ha dicho explícitamente "¡mire adentrostd
, y en ningún otro lugar"! Es por eso que algunas personas sugirieron escribir dos funciones: una como una función que se encuentra a través de ADL , y la otra para manejarstd::
calificaciones 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 el de proporcionar
std::swap
, es el trabajo de los intercambiadores asegurarse de que no usen calificadosswap
, como se indicó anteriormente. Y esto tiende a funcionar bastante bien, siempre y cuando la gente lo sepa. Pero ahí está el problema: ¡no es intuitivo necesitar usar una llamada no calificada!Para facilitar esto, algunas bibliotecas como Boost proporcionaron la función
boost::swap
, que solo realiza una llamada no calificadaswap
,std::swap
como un espacio de nombres asociado. Esto ayuda a que las cosas vuelvan a ser concisas, pero sigue siendo un fastidio.Tenga en cuenta que no hay ningún cambio en el comportamiento de C ++ 11
std::swap
, lo que yo y otros pensamos erróneamente que sería el caso. Si te mordió esto, lee aquí .En resumen: la función miembro es solo ruido, la especialización es fea e incompleta, pero la
friend
función está completa y funciona. Y cuando intercambie, useboost::swap
o no calificadoswap
constd::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 no calificadoswap
), permitiendo que se encuentre.fuente
std::vector<std::string>().swap(someVecWithData);
, lo que no es posible con unaswap
función libre porque ambos argumentos se pasan por referencia no constante.operator=
,operator+
yoperator+=
, pero claramente se acepta / espera que existan aquellos operadores en clases relevantes por simetría. Lo mismo se aplica a memberswap
+ namespace-scopedswap
en mi opinión.function<void(A*)> f; if(!f) { }
puede fallar solo porqueA
declara unoperator!
que aceptaf
igual de bien quef
el suyooperator!
(poco probable, pero puede suceder). Sifunction<>
el autor pensara "ohh tengo un 'operador bool', ¿por qué debería implementar 'operador!' ¡Eso violaría DRY!", Eso sería fatal. Solo necesita teneroperator!
implementadoA
yA
tener un constructor para afunction<...>
, y las cosas se romperán, porque ambos candidatos requerirán conversiones definidas por el usuario.Ese código es equivalente (en casi todos los sentidos) a:
Una función amiga definida dentro de una clase es:
inline
Las reglas exactas están en la sección
[class.friend]
(cito los párrafos 6 y 7 del borrador de C ++ 0x):fuente
swap
solo sea visible para ADL. Es un miembro del espacio de nombres adjunto, pero su nombre no es visible para los otros formularios de búsqueda de nombres. EDITAR: veo que @GMan fue más rápido otra vez :) @Ben siempre ha sido así en ISO C ++ :)friend
ADL solo encuentra funciones, y si solo necesitan ser funciones libres confriend
acceso, deben declararse comofriend
dentro de la clase y como una declaración de función libre normal fuera de la clase. Puede ver esa necesidad en esta respuesta , por ejemplo.