Sobrecarga de operadores de acceso de miembros ->,. *

129

Entiendo más la sobrecarga de operadores, con la excepción de los operadores de acceso miembro ->, .*, ->*etc.

En particular, ¿qué se pasa a estas funciones de operador y qué se debe devolver?

¿Cómo sabe la función del operador (por ejemplo operator->(...)) a qué miembro se hace referencia? Puede saber? ¿Es necesario saberlo?

Finalmente, ¿hay alguna consideración constante que deba tenerse en cuenta? Por ejemplo, cuando se sobrecarga algo así operator[], generalmente necesitará tanto una versión const como una no const. ¿Los operadores de acceso de miembros requieren versiones const y no const?

Bingo
fuente
1
Creo que el anterior C ++ - Preguntas a todos los toques de la Q pedido en el anterior P.
Alok Guardar
consty no se requierenconst versiones de , pero proporcionar ambas puede ser útil. operator->
Fred Foo
1
Ver también: yosefk.com/c++fqa/operator.html
György Andrasek
9
@Als: las preguntas frecuentes no explican cómo sobrecargar ->*y .*. De hecho, ¡ni siquiera los menciona! Siento que es muy raro que aparezcan en una pregunta frecuente, pero con gusto enlazaría esta pregunta desde la pregunta frecuente. ¡No cierre esto como un engaño de las preguntas frecuentes!
sbi
@sbi, simplemente no pude encontrar por completo un enlace a esta pregunta en tu (increíble) FAQ, y terminé haciendo una pregunta duplicada. ¿Podrías hacerlo más obvio? (disculpas si ya es obvio).
P i

Respuestas:

144

->

Este es el único realmente complicado. Debe ser una función miembro no estática y no requiere argumentos. El valor de retorno se utiliza para realizar la búsqueda de miembros.

Si el valor de retorno es otro objeto de tipo de clase, no un puntero, la búsqueda de miembros subsiguiente también es manejada por una operator->función. Esto se denomina "comportamiento de desglose". El idioma encadena las operator->llamadas hasta que la última devuelve un puntero.

struct client
    { int a; };

struct proxy {
    client *target;
    client *operator->() const
        { return target; }
};

struct proxy2 {
    proxy *target;
    proxy &operator->() const
        { return * target; }
};

void f() {
    client x = { 3 };
    proxy y = { & x };
    proxy2 z = { & y };

    std::cout << x.a << y->a << z->a; // print "333"
}

->*

Este solo es complicado porque no tiene nada de especial. La versión no sobrecargada requiere un objeto de puntero al tipo de clase en el lado izquierdo y un objeto de puntero al tipo de miembro a la derecha. Pero cuando lo sobrecarga, puede tomar los argumentos que desee y devolver lo que quiera. Ni siquiera tiene que ser un miembro no estático.

En otras palabras, éste es sólo un operador binario normal como +, -, y /. Ver también: ¿El operador libre -> * sobrecarga el mal?

.* y .

No se pueden sobrecargar. Ya hay un significado incorporado cuando el lado izquierdo es de tipo de clase. Quizás tenga un poco de sentido poder definirlos para un puntero en el lado izquierdo, pero el comité de diseño del lenguaje decidió que sería más confuso que útil.

La sobrecarga ->, ->*, ., y .*sólo se puede llenar en caso de que iba a ser indefinido una expresión, nunca puede cambiar el significado de una expresión que sería válido sin sobrecarga.

Agua de patata
fuente
2
Tu última declaración no es completamente cierta. Por ejemplo, puede sobrecargar el newoperador, aunque sea válido incluso cuando no esté sobrecargado.
Matt
66
@Matt bien, newsiempre está sobrecargado, o las reglas de sobrecarga realmente no se aplican a él (13.5 / 5: Las funciones de asignación y desasignación, operador nuevo, operador nuevo [], operador eliminar y operador eliminar [], se describen completamente en 3.7 0.4. los atributos y las restricciones que se encuentran en el resto de este apartado no se aplican a ellos menos que se indique explícitamente en 3.7.4.) Pero la sobrecarga unario &o binario &&, ||o ,, o la adición de sobrecargas operator=o sobrecarga casi cualquier cosa para un sin ámbito tipo de enumeración, puede cambiar el significado de una expresión. Aclaró la declaración, gracias!
Potatoswatter
41

Operador -> es especial.

"Tiene restricciones atípicas adicionales: debe devolver un objeto (o referencia a un objeto) que también tiene un operador de desreferencia de puntero, o debe devolver un puntero que se puede usar para seleccionar a qué apunta la flecha del operador de desferencia de puntero. " Bruce Eckel: Pensando CPP Vol-uno: operador->

La funcionalidad adicional se proporciona para su comodidad, por lo que no tiene que llamar

a->->func();

Simplemente puedes hacer:

a->func();

Eso hace que el operador -> sea diferente de las otras sobrecargas del operador.

Totonga
fuente
3
Esta respuesta merece más crédito, puede descargar el libro de Eckel desde ese enlace y la información se encuentra en el capítulo 12 del volumen uno.
P i
26

No puede sobrecargar el acceso de miembros .(es decir, la segunda parte de lo que ->hace). Sin embargo, puede sobrecargar el unario desreferenciar operador *(es decir, la primera parte de lo que ->lo hace).

El ->operador C ++ es básicamente la unión de dos pasos y esto está claro si cree que x->yes equivalente a (*x).y. C ++ le permite personalizar qué hacer con la (*x)parte cuando xes una instancia de su clase.

La semántica para la ->sobrecarga es algo extraña porque C ++ le permite devolver un puntero regular (que se usará para encontrar el objeto puntiagudo) o devolver una instancia de otra clase si esta clase también proporciona un ->operador. Cuando en este segundo caso, la búsqueda del objeto desreferenciado continúa desde esta nueva instancia.

6502
fuente
2
¡Gran explicación! Supongo que eso significa lo mismo ->*, ya que es equivalente a la forma de (*x).*?
Bingo
10

El ->operador no sabe a qué miembro se está señalando, solo proporciona un objeto para realizar el acceso real del miembro.

Además, no veo ninguna razón por la que no pueda proporcionar versiones const y no const.

John Chadwick
fuente
7

Cuando sobrecarga el operador -> () (no se pasan argumentos aquí), lo que el compilador realmente hace es llamar -> recursivamente hasta que devuelva un puntero real a un tipo. Luego usa el miembro / método correcto.

Esto es útil, por ejemplo, para crear una clase de puntero inteligente que encapsule el puntero real. Se llama al operador sobrecargado->, hace lo que sea que haga (por ejemplo, bloqueo por seguridad de subprocesos), devuelve el puntero interno y luego el compilador llama -> para este puntero interno.

En cuanto a la constidad, se ha respondido en los comentarios y otras respuestas (puede y debe proporcionar ambas).

Asaf
fuente