¿Cuáles son las reglas básicas y los modismos para la sobrecarga del operador?

Nota Las respuestas se dieron en un orden específico, pero dado que muchos usuarios clasifican las respuestas según los votos, no el momento en que se dieron, aquí está el índice de las respuestas en el orden en el que tienen más sentido:

<sub> (Nota. Esto significa que está ingresando a las Preguntas frecuentes sobre el desbordamiento de la pila de C ++ . Si desea criticar la idea de proporcionar una pregunta frecuente en este formulario, luego publicar en el meta que lo inició todo sería el lugar para hacerlo. Las respuestas a esta pregunta se rastrean en el chat de C ++ , donde se inició la idea de las preguntas frecuentes en primer lugar, por lo que lo más probable es que la respuesta la lean aquellos que propusieron esta idea.)

1936
12 дек. set sbi 12 dec. 2010-12-12 15:44 '10 a las 15:44 2010-12-12 15:44
@ 7 respuestas

Operadores generales para recarga.

La mayor parte del trabajo de los operadores de sobrecarga es el código de la sala de calderas. Esto no es sorprendente, ya que los operadores son simplemente azúcar sintáctica, su trabajo real puede realizarse (y con frecuencia dirigido) a funciones simples. Pero es importante que obtenga el código de la caldera. Si falla, su código de operador no se compilará, o el código de usuario no se compilará, o el código de usuario parecerá sorprendente.

Operador de asignación

Se puede decir mucho sobre la cita. Sin embargo, la mayoría de ellos ya se mencionaron en las famosas "Preguntas frecuentes sobre cambiar y cambiar" , por lo que omitiré la mayor parte de esto aquí, solo enumeraré el operador de asignación ideal para el enlace:

 X X::operator=(X rhs) { swap(rhs); return *this; } 

Operadores Bitshift (utilizados para Stream I / O)

Los operadores de cambio de bits << y >> , aunque todavía se utilizan en la interfaz de hardware para las funciones de manipulación de bits que heredan de C, se han vuelto más comunes como operadores de flujo de entrada y salida sobrecargados en la mayoría de las aplicaciones. Para obtener instrucciones de sobrecarga como operadores de manipulación de bits, consulte la siguiente sección sobre operaciones aritméticas binarias. Para implementar su propio formato personalizado y lógica de análisis cuando su objeto se utiliza con iostreams, continúe.

Los operadores de flujo, entre los operadores recargables más frecuentemente, son operadores de infijo binarios, para los cuales la sintaxis no indica restricciones sobre si deben ser miembros o no miembros. Debido a que cambian su argumento de la izquierda (cambian el estado de los hilos), deben implementarse, de acuerdo con las reglas generales, como miembros de su tipo de operandos de la izquierda. Sin embargo, sus operandos de la izquierda son flujos de la biblioteca estándar y, aunque la mayoría de los operadores de entrada y salida de flujo definidos por la biblioteca estándar se definen como miembros de las clases de subprocesos, cuando implementa operaciones de entrada y salida para sus propios tipos, no puede cambiar los tipos estándar de flujos de biblioteca. . Es por eso que necesita implementar estos operadores para sus propios tipos como funciones no miembros. Las formas canónicas de estos dos son:

 std::ostream operator<<(std::ostream os, const T obj) { // write obj to stream return os; } std::istream operator>>(std::istream is, T obj) { // read obj from stream if(  ) is.setstate(std::ios::failbit); return is; } 

Cuando se implementa el operator>> manualmente, es necesario establecer el estado de los hilos solo cuando se realiza la lectura en sí, pero el resultado no coincide con el esperado.

Operador de llamada de función

El operador de llamada de función utilizado para crear objetos funcionales, también conocidos como functores, debe definirse como una función miembro , por lo que siempre tiene implícito this argumento de funciones miembro. Además, se puede sobrecargar para aceptar cualquier número de argumentos adicionales, incluido el cero.

Aquí hay un ejemplo de la sintaxis:

 class foo { public: // Overloaded call operator int operator()(const std::string y) { // ... } }; 

Uso:

 foo f; int a = f("hello"); 

En la biblioteca estándar de C ++, los objetos objeto siempre se copian. Por lo tanto, sus propios objetos de función deberían ser baratos para copiar. Si es absolutamente necesario que el objeto de función utilice datos que son costosos de copiar, es mejor almacenar estos datos en otro lugar y referirse al objeto de función.

Operadores de comparación

Los operadores de comparación de infijos binarios deben, según las reglas del pulgar, implementarse como funciones no miembros 1 . ! Negación de prefijo unitario ! debe (según las mismas reglas) implementarse como una función miembro. (pero, como regla general, no se recomienda sobrecargarlo).

Los algoritmos de biblioteca estándar (por ejemplo, std::sort() ) y los tipos (por ejemplo, std::map ) siempre esperarán al operator< . Sin embargo, los usuarios de su tipo esperan que todos los demás operadores también estén presentes, por lo que si define el operator< , asegúrese de seguir la tercera regla básica de sobrecarga del operador y también determine todos los demás operadores de comparación lógica. La forma canónica de implementarlas es la siguiente:

 inline bool operator==(const X lhs, const X rhs){  } inline bool operator!=(const X lhs, const X rhs){return !operator==(lhs,rhs);} inline bool operator< (const X lhs, const X rhs){  } inline bool operator> (const X lhs, const X rhs){return operator< (rhs,lhs);} inline bool operator<=(const X lhs, const X rhs){return !operator> (lhs,rhs);} inline bool operator>=(const X lhs, const X rhs){return !operator< (lhs,rhs);} 

Es importante tener en cuenta que solo dos de estos operadores realmente hacen algo, otros simplemente no redirigen sus argumentos a ninguno de estos dos para hacer el trabajo real.

La sintaxis para sobrecargar los operadores booleanos binarios restantes ( || , > ) sigue las reglas de los operadores de comparación. Sin embargo, es muy poco probable que encuentre un precedente razonable para estos 2 .

1 Al igual que con todas las reglas generales, a veces puede haber razones para romper esto. Si este es el caso, no olvide que el operando izquierdo de los operadores de comparación binarios, para los cuales para funciones miembro es *this , también debe ser const . Por lo tanto, el operador de comparación, implementado como una función miembro, debe tener esta firma:

 bool operator<(const X rhs) const {  } 

(Tenga en cuenta la const al final.)

2 Cabe señalar que la versión incrustada || y > usa semántica de etiquetas. Aunque definido por el usuario (ya que son azúcares sintácticos para llamadas a métodos), no use la semántica de etiquetas. El usuario esperará que estos operadores tengan una semántica de etiquetas, y su código puede depender de ello. Por lo tanto, se recomienda encarecidamente que NUNCA los identifique.

Operadores aritméticos

Operadores aritméticos únicos

Los operadores de incremento y decremento únicos están presentes tanto en el prefijo como en el postfix. Para distinguir una de la otra, las variantes de postfix toman el argumento opcional del int dummy. Si está sobrecargando incrementos o decrementos, asegúrese de usar siempre las versiones de prefijo y postfijo. Aquí está la implementación canónica del incremento, el decremento sigue las mismas reglas:

 class X { X operator++() { // do actual increment return *this; } X operator++(int) { X tmp(*this); operator++(); return tmp; } }; 

Tenga en cuenta que la variante de posfijo se implementa en términos del prefijo. También tenga en cuenta que postfix realiza una copia adicional. 2

La sobrecarga de menos y más unario no es muy común y probablemente se evite. Si es necesario, probablemente deberían estar sobrecargados como funciones miembro.

2 También tenga en cuenta que la opción postfix hace más trabajo y por lo tanto es menos eficiente de usar que la versión de prefijo. Esta es una buena razón, como regla general, prefiere incrementar el prefijo incrementando el postfijo. Aunque los compiladores generalmente pueden optimizar el trabajo incremental adicional para los tipos incorporados, es posible que no puedan hacer lo mismo para los tipos personalizados (que pueden ser algo inocentes, como un iterador de listas). Después de que esté acostumbrado a hacer i++ , es muy difícil no olvidarse de hacerlo en lugar de ++i lugar de no tener un tipo incorporado (además, tendrá que cambiar el código al cambiar el tipo), por lo que es mejor hacer un hábito de usar siempre el incremento de prefijo si no es claramente el postfix requerido

Operadores aritméticos binarios

Para operadores aritméticos binarios, no olvide obedecer la tercera sobrecarga del operador de la regla principal: si proporciona + , también proporcione += , si proporciona - , no se pierda -= , etc. Se dice que Andrei Koenig fue el primero en darse cuenta de que los agentes de asignación complejos pueden utilizarse como base para sus copias no compuestas. Es decir, el operador + se implementa en términos de += , - implementa en términos de -= , etc.

De acuerdo con nuestras reglas generales, + y sus satélites deben ser no miembros, mientras que sus comparaciones compuestas ( += , etc.), que cambian su argumento izquierdo, deben ser miembros. Aquí hay un código de ejemplo para += y + , otros operadores aritméticos binarios deben implementarse de la misma manera:

 class X { X operator+=(const X rhs) { // actual addition of rhs to *this return *this; } }; inline X operator+(X lhs, const X rhs) { lhs += rhs; return lhs; } 

operator+= devuelve su resultado por referencia, y operator+ devuelve una copia de su resultado. Por supuesto, devolver un enlace suele ser más eficiente que devolver una copia, pero en el caso del operator+ no hay forma de copiarlo. Cuando escribe a + b , espera que el resultado sea un nuevo valor, por lo que el operator+ debe devolver un nuevo valor. 3 También tenga en cuenta que el operator+ acepta su operando izquierdo como una copia , no como una referencia constante. La razón para esto es la misma que indica que para el operator= su argumento se toma como una copia.

Operadores de manipulación de bits ~ > | ^ << >> debe implementarse de la misma manera que los operadores aritméticos. Sin embargo (con la excepción de la sobrecarga << y >> para salida y entrada) hay muy pocos usos razonables para la sobrecarga.

3 Nuevamente, la lección que se debe aprender de esto es que a += b es generalmente más efectivo que a + b y debería ser preferido si es posible.

Conteo de matrices

El operador de índice de matriz es un operador binario que debe implementarse como miembro de la clase. Se utiliza para tipos de contenedores que le permiten acceder a sus elementos de datos con una clave. La forma canónica de su disposición es la siguiente:

 class X { value_type operator[](index_type idx); const value_type operator[](index_type idx) const; // ... }; 

Si no desea que los usuarios de su clase cambien los elementos de datos devueltos por el operator[] (en este caso, puede omitir la opción no constante), siempre debe proporcionar ambas versiones del operador.

Si se sabe que value_type se refiere al tipo incorporado, la variante constante del operador debe devolver una copia en lugar de la referencia constante.

Operadores para tipos de punteros

Para definir sus propios iteradores o punteros inteligentes, debe sobrecargar el operador de marcado de prefijo único * y el operador de acceso al infijo del miembro del puntero binario -> :

 class my_ptr { value_type operator*(); const value_type operator*() const; value_type* operator->(); const value_type* operator->() const; }; 

Tenga en cuenta que también casi siempre requieren una versión constante y otra no constante. Para el operador -> , si el value_type es de tipo class (o struct o union ), otro operator->() se llama de forma recursiva, mientras que operator->() no devuelve un valor que no sea de clase.

La dirección única del operador nunca debe sobrecargarse.

Para el operator->*() vea esta pregunta . Rara vez se usa y, por lo tanto, rara vez se sobrecarga. De hecho, incluso los iteradores no lo sobrecargan.


Continuar Operadores de Conversión

945
12 дек. la respuesta se da sbi 12 dec. 2010-12-12 15:47 '10 a las 15:47 2010-12-12 15:47

Tres reglas básicas para la sobrecarga de operadores en C ++

Cuando se trata de la sobrecarga de operadores en C ++, hay tres reglas básicas que debe seguir . Al igual que con todas estas reglas, de hecho hay excepciones. Algunas veces las personas se desviaban de ellos, y el resultado era un buen código, pero tales desviaciones positivas son pocas y distantes entre sí. Al menos 99 de las 100 anomalías que vi eran infundadas. Sin embargo, podría ser lo mismo que 999 de 1000. Por lo tanto, es mejor que sigas las siguientes reglas.

  • Siempre que el significado del operador sea claramente incierto e innegable, no debe sobrecargarse. En su lugar, proporcionar una función con un nombre bien elegido. En principio, la primera y más importante regla para las sobrecargas de operadores, en su corazón, dice: "No hagas esto. Esto puede parecer extraño porque se sabe mucho sobre la sobrecarga de operadores y, por lo tanto, muchos artículos, capítulos de libros y otros textos se relacionan con todo esto. Pero a pesar de esta evidencia aparentemente obvia, hay pocos casos sorprendentemente apropiados en que la sobrecarga del operador sea apropiada. La razón es que en realidad es difícil entender la semántica subyacente a la aplicación del operador si el uso del operador eno aplicación no es muy conocido e indiscutible. Contrariamente a la creencia popular, esto casi nunca es el caso.

  • Siempre adherirse a operadores semánticos conocidos.
    C ++ no crea restricciones en la semántica de los operadores sobrecargados. Su compilador aceptará con gusto un código que implementa el operador binario + para restar su operando de la derecha. Sin embargo, los usuarios de dicho operador nunca sospecharán que la expresión a + b reste a de b . Por supuesto, esto supone que la semántica del operador en el dominio de la aplicación es innegable.

  • Siempre proporcione todo el conjunto de operaciones relacionadas.
    Los operadores están asociados entre sí y con otras operaciones. Si su tipo admite a + b , los usuarios esperan que también puedan llamar a += b . Si es compatible con incrementar el prefijo de ++a , ellos también esperan que a++ funcione. Si pueden verificar si hay a < b , probablemente también podrán verificar si hay a > b . Si pueden copiar y construir su tipo, esperan que la asignación también funcione.


Continuar la decisión entre el miembro y el no miembro .

459
12 дек. la respuesta se da sbi 12 dec. 2010-12-12 15:45 '10 a las 15:45 2010-12-12 15:45

Sintaxis general para la sobrecarga del operador en C ++

No puede cambiar el valor de los operadores para los tipos incorporados en C ++, los operadores pueden sobrecargarse solo para los tipos personalizados 1 . Es decir, al menos uno de los operandos debe ser de un tipo definido por el usuario. Al igual que con otras funciones sobrecargadas, los operadores pueden sobrecargarse para un conjunto específico de parámetros una sola vez.

No todos los operadores se pueden sobrecargar en C ++. Entre los operadores que no pueden ser sobrecargados :. :: sizeof typeid .* y el único operador ternario en C ++, ?:

Entre los operadores que pueden estar sobrecargados en C ++ se encuentran los siguientes:

  • operadores aritméticos: + - * / % y += -= *= /= %= (todos los infijos binarios); + - (prefijo unario); ++ -- (prefijo unario y postfix)
  • manipulación de bits: > | ^ << >> y > |= ^= <<= >>= (todos los infijos binarios); ~ (prefijo unario)
  • Álgebra booleana: == != < > <= >= || > (todo infijo binario); ! (prefijo unario)
  • Gestión de memoria: new new[] delete delete[]
  • Operadores de conversión implícita
  • collection: = [] -> ->* , (todos los infijos binarios); * > (todos los prefijos unarios) () (llamada de función, infijo n-ary)

Sin embargo, el hecho de que pueda sobrecargar todo esto no significa que tenga que hacerlo. Ver las reglas básicas de sobrecarga del operador.

En C ++, los operadores están sobrecargados como funciones con nombres especiales . Como en el caso de otras funciones, los operadores sobrecargados generalmente pueden implementarse como una función de un miembro de su operando izquierdo de tipo , o como funciones no miembros . Independientemente de si puede elegir o usar uno de ellos, depende de varios criterios. 2 El operador unario @ 3 aplicado al objeto x se llama como operator@(x) o como x.operator@() . El operador de infijo binario @ , aplicado a los objetos y , se llama como operator@(x,y) o x.operator@(y) . 4

Los operadores que se implementan como funciones no miembros a veces son amigos de su tipo de operando.

1 El término "definido por el usuario" puede ser un poco engañoso. C ++ hace una distinción entre los tipos incorporados y los tipos definidos por el usuario. El primero incluye, por ejemplo, int, char y double; los últimos incluyen todos los tipos de estructura, clase, unión y enumeración, incluida la biblioteca estándar, incluso si no están, como tales, definidos por los usuarios.

2 Esto se describe en la parte posterior de esta pregunta frecuente.

3 @ no @ un operador de C ++ válido, así que lo uso como un marcador de posición.

4 Un solo operador ternario en C ++ no puede ser sobrecargado, y un solo operador n-ario siempre debe implementarse como una función miembro.


Continuar Las tres reglas básicas para la sobrecarga de operadores en C ++ .

242
12 дек. la respuesta se da sbi 12 dec. 2010-12-12 15:46 '10 a las 15:46 2010-12-12 15:46

Decisión entre miembro y no miembro

Operadores binarios = (asignación), [] (suscripción a matrices), -> (acceso de miembros) y el operador n-ary () (llamada de función) siempre deben implementarse como funciones miembro , ya que requieren la sintaxis del lenguaje .

Otros operadores pueden implementarse como miembros o como no miembros. Sin embargo, algunos de ellos generalmente tienen que implementarse como funciones que no son miembros, ya que usted no puede cambiar su operando izquierdo. Los más notables de estos son los operadores de entrada y salida << y >> , cuyos operandos de la izquierda son clases de flujos de la biblioteca estándar que no se pueden cambiar.

Для всех операторов, где вам нужно либо реализовать их как функцию-член, либо не-членную функцию, использовать следующие правила большого пальца :

  • Если это унарный оператор , реализуйте его как функцию member .
  • Если двоичный оператор обрабатывает оба операнда одинаково (он оставляет их неизменными), реализуйте этот оператор как функцию не-член .
  • Если двоичный оператор не обрабатывает оба его операнда равно (обычно это изменяет его левый операнд), может быть полезно сделать его член функции его левого операнда типа, если он должен получить доступ к частным частям операнда.