c++ - sencillos - sobrecarga de operadores java
¿Cuáles son las reglas básicas y los modismos para la sobrecarga de operadores? (7)
La sintaxis general de la sobrecarga de operadores en C ++
No puede cambiar el significado de los operadores para los tipos incorporados en C ++, los operadores solo pueden sobrecargarse para los tipos 1 definidos por el usuario. 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 determinado conjunto de parámetros una sola vez.
No todos los operadores se pueden sobrecargar en C ++. Entre los operadores que no pueden ser sobrecargados se encuentran:. ::
sizeof
typeid
.*
y el único operador ternario en C ++, ?:
Entre los operadores que pueden sobrecargarse en C ++ se encuentran estos:
- 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
- miscelánea:
=
[]
->
->*
,
(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 deba hacerlo. Ver las reglas básicas de sobrecarga de operadores.
En C ++, los operadores están sobrecargados en forma de funciones con nombres especiales . Al igual que con otras funciones, los operadores sobrecargados generalmente pueden implementarse como una función miembro del tipo de su operando izquierdo o como funciones no miembros . Si usted es libre de elegir o está obligado a usar cualquiera de los dos, depende de varios criterios. 2 Un operador unario @
3 , aplicado a un objeto x, se invoca como operator@(x)
o como x.operator@()
. Un operador de infijo binario @
, aplicado a los objetos y
, se llama como operator@(x,y)
o como x.operator@(y)
. 4
Los operadores que se implementan como funciones no miembros a veces son amigos del tipo de su operando.
1 El término "definido por el usuario" puede ser un poco engañoso. C ++ hace la distinción entre tipos incorporados y tipos definidos por el usuario. A los primeros pertenecen, por ejemplo, int, char y double; a este último pertenecen todos los tipos de estructura, clase, unión y enumeración, incluidos los de la biblioteca estándar, aunque no estén definidos, como tales, por los usuarios.
2 Esto se trata en una parte posterior de esta pregunta frecuente.
3 El @
no es un operador válido en C ++, por eso lo uso como marcador de posición.
4 El único operador ternario en C ++ no se puede sobrecargar y el único operador n-ario siempre debe implementarse como una función miembro.
Continúe con las tres reglas básicas de sobrecarga de operadores en C ++ .
Nota: Las respuestas se dieron en un orden específico , pero dado que muchos usuarios clasifican las respuestas según los votos, en lugar del momento en que se dieron, aquí hay un índice de las respuestas en el orden en el que tienen más sentido:
- La sintaxis general de la sobrecarga de operadores en C ++
- Las tres reglas básicas de sobrecarga de operadores en C ++
- La Decisión entre Miembro y No miembro.
- Operadores comunes para sobrecargar
- Operador de Asignación
- Operadores de entrada y salida
- Operador de llamada de función
- Operadores de comparación
- Operadores aritméticos
- Matriculación de suscripciones
- Operadores para tipos tipo puntero
- Operadores de conversión
- Sobrecarga de nuevo y borrar
(Nota: se pretende que sea una entrada a las Preguntas frecuentes sobre C ++ de Stack Overflow . Si desea criticar la idea de proporcionar una Pregunta frecuente en este formulario, la publicación en el meta que inició todo esto sería el lugar para hacerlo. esa pregunta se monitorea en la sala de chat de C ++ , donde comenzó la idea de las preguntas frecuentes en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos a quienes se les ocurrió la idea.)
Las tres reglas básicas de 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 han desviado de ellos y el resultado no fue un código malo, pero tales desviaciones positivas son pocas y distantes entre sí. Por lo menos, 99 de cada 100 desviaciones que he visto estaban injustificadas. Sin embargo, podría haber sido 999 de 1000. Así que es mejor que te limites a las siguientes reglas.
Siempre que el significado de un operador no sea claro e indiscutible, no debe sobrecargarse. En su lugar, proporcionar una función con un nombre bien elegido.
Básicamente, la primera y más importante regla para sobrecargar a los operadores, en su corazón, dice: No lo hagas . Esto puede parecer extraño, porque hay mucho que saber sobre la sobrecarga de operadores y, por lo tanto, muchos artículos, capítulos de libros y otros textos tratan todo esto. Pero a pesar de esta evidencia aparentemente obvia, hay solo unos pocos casos sorprendentemente apropiados donde la sobrecarga de operadores es apropiada . La razón es que en realidad es difícil entender la semántica detrás de la aplicación de un operador, a menos que el uso del operador en el dominio de la aplicación sea bien conocido e indiscutible. Contrariamente a la creencia popular, este casi nunca es el caso.Siempre apegarse a la semántica conocida del operador.
C ++ no plantea limitaciones en la semántica de los operadores sobrecargados. Su compilador aceptará felizmente el código que implementa el operador binario+
para restar de su operando derecho. Sin embargo, los usuarios de tal operador nunca sospecharían que la expresióna + b
restaraa
deb
. Por supuesto, esto supone que la semántica del operador en el dominio de la aplicación es indiscutible.Siempre proporcionar todo de un conjunto de operaciones relacionadas.
Los operadores están relacionados entre sí y con otras operaciones. Si su tipo admitea + b
, los usuarios también podrán llamara += b
. Si admite el incremento de prefijo++a
, esperarán quea++
funcione también. Si pueden verificar sia < b
, seguramente esperarán también poder verificar sia > b
. Si pueden copiar y construir su tipo, esperan que la asignación también funcione.
Continuar con la decisión entre el miembro y el no miembro .
Operadores comunes para sobrecargar
La mayor parte del trabajo en los operadores de sobrecarga es el código de placa de caldera. Eso no es de extrañar, ya que los operadores son simplemente azúcar sintáctica, su trabajo real podría realizarse mediante (y con frecuencia se envía a) funciones simples. Pero es importante que obtenga este código de placa de caldera correctamente. Si falla, el código de su operador no se compilará o el código de sus usuarios no se compilará o el código de sus usuarios se comportará sorprendentemente.
Operador de Asignación
Hay mucho que decir acerca de la asignación. Sin embargo, la mayor parte de esto ya se ha dicho en las famosas Preguntas frecuentes sobre copia e intercambio de GMan , por lo que me saltaré la mayor parte aquí, solo enumeraré al operador de asignación perfecto como referencia:
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 frecuentes como operadores de entrada y salida de flujo sobrecargados en la mayoría de las aplicaciones. Para obtener información sobre la sobrecarga de guía como operadores de manipulación de bits, consulte la siguiente sección sobre Operadores aritméticos binarios. 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 más sobrecargados, son operadores de infijo binarios para los que la sintaxis no especifica ninguna restricción sobre si deben ser miembros o no miembros. Dado que cambian su argumento a la izquierda (alteran el estado de la secuencia), deben, según las reglas generales, implementarse como miembros del tipo de su operando izquierdo. Sin embargo, sus operandos de la izquierda son secuencias de la biblioteca estándar, y si bien la mayoría de los operadores de entrada y salida de secuencia definidos por la biblioteca estándar se definen como miembros de las clases de secuencia, cuando implementa operaciones de salida y entrada para sus propios tipos, no se pueden cambiar los tipos de flujo de la biblioteca estándar. Es por eso que necesita implementar estos operadores para sus propios tipos como funciones no miembros. Las formas canónicas de los dos son estas:
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( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
Cuando se implementa el operator>>
, el ajuste manual del estado de la secuencia solo es necesario cuando la lectura se realiza correctamente, pero el resultado no es el que se esperaría.
Operador de llamada de función
El operador de llamada de función, que se utiliza para crear objetos de función, también conocidos como functores, debe definirse como una función miembro , por lo que siempre tiene el argumento implícito de funciones miembro. Aparte de esto, se puede sobrecargar para tomar 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");
A lo largo de la biblioteca estándar de C ++, los objetos de función siempre se copian. Por lo tanto, sus propios objetos de función deberían ser baratos para copiar. Si un objeto de función necesita absolutamente utilizar datos que son costosos de copiar, es mejor almacenar esos datos en otro lugar y hacer que el objeto de función se refiera a ellos.
Operadores de comparación
Los operadores de comparación de infijos binarios deben, según las reglas generales, implementarse como funciones no miembros 1 . La negación del prefijo único !
debe (según las mismas reglas) implementarse como una función miembro. (pero generalmente no es una buena idea sobrecargarlo).
Los algoritmos de la biblioteca estándar (por ejemplo, std::sort()
) y los tipos (por ejemplo, std::map
) siempre esperarán que el operator<
esté presente. Sin embargo, los usuarios de su tipo también esperarán que todos los demás operadores estén presentes , por lo que si define el operator<
, asegúrese de seguir la tercera regla fundamental de la sobrecarga de operadores y también defina todos los demás operadores de comparación booleanos. La forma canónica de implementarlas es esta:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
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);}
Lo importante a tener en cuenta aquí es que solo dos de estos operadores realmente hacen algo, los otros simplemente están enviando sus argumentos a cualquiera 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 caso de uso razonable para estos 2 .
1 Al igual que con todas las reglas generales, a veces puede haber razones para romper esta también. Si es así, no olvide que el operando de la izquierda de los operadores de comparación binarios, que para las funciones miembro será *this
, también debe ser const
. Entonces, un operador de comparación implementado como una función miembro tendría que tener esta firma:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(Tenga en cuenta la const
al final.)
2 Cabe señalar que la versión incorporada de ||
y &&
usa semántica de atajo. Mientras que los definidos por el usuario (debido a que son azúcares sintácticos para las llamadas de métodos) no se usa la semántica de acceso directo. El usuario esperará que estos operadores tengan una semántica de acceso directo, y su código puede depender de ello, por lo tanto, se recomienda NUNCA definirlos.
Operadores aritméticos
Operadores aritméticos únicos
Los operadores unarios de incremento y decremento vienen en prefijo y postfix. Para distinguir una de la otra, las variantes de postfix toman un argumento dummy int adicional. Si sobrecarga el incremento o la disminución, asegúrese de implementar siempre las versiones de prefijo y postfijo. Aquí está la implementación canónica del incremento, la disminución 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 de prefijo. También tenga en cuenta que postfix hace una copia adicional. 2
Sobrecargar unario menos y más no es muy común y, probablemente, es mejor evitarlo. Si es necesario, probablemente deberían estar sobrecargados como funciones miembro.
2 También tenga en cuenta que la variante de posfijo hace más trabajo y, por lo tanto, es menos eficiente de usar que la variante de prefijo. Esta es una buena razón para preferir generalmente el incremento de prefijo sobre el incremento de postfix. Si bien los compiladores generalmente pueden optimizar el trabajo adicional del incremento de postfix para los tipos incorporados, es posible que no puedan hacer lo mismo para los tipos definidos por el usuario (lo que podría ser algo tan inocentemente como un iterador de lista). Una vez que te acostumbras a hacer i++
, se vuelve muy difícil recordar hacer ++i
cambio cuando no soy de un tipo incorporado (además, tendrías que cambiar el código al cambiar de tipo), por lo que es mejor haga un hábito de usar siempre el incremento de prefijo, a menos que se necesite explícitamente postfix.
Operadores aritméticos binarios
Para los operadores aritméticos binarios, no olvide obedecer la sobrecarga del tercer operador de la regla básica: Si proporciona +
, también proporcione +=
, si proporciona -
, no omita -=
, etc. Se dice que Andrew Koenig fue el primero para observar que los operadores de asignación compuesta se pueden utilizar como base para sus contrapartes no compuestas. Es decir, el operador +
se implementa en términos de +=
, -
se implementa en términos de -=
etc.
De acuerdo con nuestras reglas generales, +
y sus acompañantes deben ser no miembros, mientras que sus contrapartes de asignación compuesta ( +=
etc.), cambiando su argumento izquierdo, deben ser miembros. Aquí está el código ejemplar para +=
y +
, los 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, mientras que operator+
devuelve una copia de su resultado. Por supuesto, devolver una referencia suele ser más eficiente que devolver una copia, pero en el caso del operator+
, no hay forma de evitar la copia. 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+
toma su operando izquierdo por copia en lugar de por referencia constante. La razón para esto es la misma que la razón para que el operator=
tome su argumento por copia.
Los operadores de manipulación de bits ~
&
|
^
<<
>>
debe implementarse de la misma manera que los operadores aritméticos. Sin embargo, (excepto la sobrecarga <<
y >>
para salida y entrada) hay muy pocos casos de uso razonables para sobrecargar estos.
3 Nuevamente, la lección que se debe sacar de esto es que a += b
es, en general, más eficiente que a + b
y debería ser preferible si es posible.
Matriculación de suscripciones
El operador de subíndice de matriz es un operador binario que debe implementarse como un miembro de clase. Se utiliza para tipos tipo contenedor que permiten el acceso a sus elementos de datos mediante una clave. La forma canónica de proporcionar estos es esta:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
A menos que no desee que los usuarios de su clase puedan cambiar los elementos de datos devueltos por el operator[]
(en cuyo caso puede omitir la variante no constante), siempre debe proporcionar ambas variantes del operador.
Si se sabe que value_type se refiere a un tipo incorporado, la variante constante del operador debe devolver una copia en lugar de una referencia constante.
Operadores para tipos tipo puntero
Para definir sus propios iteradores o punteros inteligentes, debe sobrecargar el operador único de desreferenciación de prefijos *
y el operador de acceso a miembros del puntero de infijo binario ->
:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
Tenga en cuenta que estos, también, casi siempre necesitarán tanto una versión constante como una versión no constante. Para el operador ->
, si value_type
es de class
(o struct
o union
), otro operator->()
se llama de forma recursiva, hasta que un operator->()
devuelve un valor de tipo que no es de clase.
La dirección única del operador nunca debe sobrecargarse.
Para el operator->*()
vea esta pregunta . Rara vez se utiliza y por lo tanto rara vez se sobrecarga. De hecho, incluso los iteradores no lo sobrecargan.
Continuar a los operadores de conversión
Operadores de conversión (también conocidos como conversiones definidas por el usuario)
En C ++ puede crear operadores de conversión, operadores que permiten al compilador convertir entre sus tipos y otros tipos definidos. Hay dos tipos de operadores de conversión, los implícitos y los explícitos.
Operadores de conversión implícitos (C ++ 98 / C ++ 03 y C ++ 11)
Un operador de conversión implícita le permite al compilador convertir implícitamente (como la conversión entre int
y long
) el valor de un tipo definido por el usuario a algún otro tipo.
La siguiente es una clase simple con un operador de conversión implícito:
class my_string {
public:
operator const char*() const {return data_;} // This is the conversion operator
private:
const char* data_;
};
Los operadores de conversión implícita, como los constructores de un argumento, son conversiones definidas por el usuario. Los compiladores otorgarán una conversión definida por el usuario cuando intenten hacer coincidir una llamada con una función sobrecargada.
void f(const char*);
my_string str;
f(str); // same as f( str.operator const char*() )
Al principio, esto parece muy útil, pero el problema con esto es que la conversión implícita incluso se activa cuando no se espera. En el siguiente código, se llamará void f(const char*)
porque my_string()
no es un lvalue , por lo que el primero no coincide:
void f(my_string&);
void f(const char*);
f(my_string());
Los principiantes se equivocan fácilmente e incluso los programadores experimentados de C ++ a veces se sorprenden porque el compilador escoge una sobrecarga que no sospecharon. Estos problemas pueden ser mitigados por operadores de conversión explícitos.
Operadores de conversión explícitos (C ++ 11)
A diferencia de los operadores de conversión implícitos, los operadores de conversión explícitos nunca se activarán cuando no espere que lo hagan. La siguiente es una clase simple con un operador de conversión explícito:
class my_string {
public:
explicit operator const char*() const {return data_;}
private:
const char* data_;
};
Note lo explicit
. Ahora, cuando intenta ejecutar el código inesperado de los operadores de conversión implícitos, obtiene un error del compilador:
prog.cpp: In function ‘int main()’: prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’ prog.cpp:15:18: note: candidates are: prog.cpp:11:10: note: void f(my_string&) prog.cpp:11:10: note: no known conversion for argument 1 from ‘my_string’ to ‘my_string&’ prog.cpp:12:10: note: void f(const char*) prog.cpp:12:10: note: no known conversion for argument 1 from ‘my_string’ to ‘const char*’
Para invocar el operador de static_cast
explícito, debe usar static_cast
, una static_cast
de estilo C o una static_cast
de estilo de constructor (es decir, T(value)
).
Sin embargo, hay una excepción a esto: el compilador tiene permitido convertir implícitamente a bool
. Además, al compilador no se le permite hacer otra conversión implícita después de que se convierta a bool
(a un compilador se le permite hacer 2 conversiones implícitas a la vez, pero solo 1 conversión definida por el usuario como máximo).
Debido a que el compilador no emitirá bools "pasados", los operadores de conversión explícitos ahora eliminan la necesidad del idioma Safe Bool . Por ejemplo, los punteros inteligentes antes de C ++ 11 utilizaban el lenguaje Safe Bool para evitar conversiones a tipos integrales. En C ++ 11, los punteros inteligentes usan un operador explícito en su lugar porque el compilador no puede convertir implícitamente a un tipo integral después de que haya convertido explícitamente un tipo a bool.
Continuar con la sobrecarga de new
y delete
.
Sobrecarga de new
y delete
Nota: Esto solo trata con la sintaxis de la sobrecarga new
y la delete
, no con la implementación de dichos operadores sobrecargados. Creo que la semántica de sobrecargar new
y delete
merece sus propias preguntas frecuentes , dentro del tema de la sobrecarga de operadores, nunca podré hacer justicia.
Lo esencial
En C ++, cuando escribe una nueva expresión como new T(arg)
, suceden dos cosas cuando se evalúa esta expresión: el primer operator new
se invoca para obtener memoria en bruto, y luego se invoca al constructor apropiado de T
para convertir esta memoria en bruto en un objeto valido Del mismo modo, cuando elimina un objeto, primero se llama a su destructor, y luego la memoria se devuelve al operator delete
.
C ++ le permite ajustar ambas operaciones: administración de la memoria y la construcción / destrucción del objeto en la memoria asignada. Lo último se hace escribiendo constructores y destructores para una clase. La gestión de la memoria de ajuste fino se realiza escribiendo su propio operator new
y el operator delete
.
La primera de las reglas básicas de la sobrecarga de operadores, no lo haga, se aplica especialmente a la sobrecarga de new
y delete
. Casi las únicas razones para sobrecargar a estos operadores son problemas de rendimiento y limitaciones de memoria , y en muchos casos, otras acciones, como los cambios en los algoritmos utilizados, proporcionarán una relación costo / ganancia mucho mayor que intentar modificar la administración de memoria.
La biblioteca estándar de C ++ incluye un conjunto predefinido de operadores new
y de delete
. Los más importantes son estos:
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
void* operator new[](std::size_t) throw(std::bad_alloc);
void operator delete[](void*) throw();
Los dos primeros asignan / desasignan memoria para un objeto, los dos últimos para una matriz de objetos. Si proporciona sus propias versiones de estos, no se sobrecargarán, sino que reemplazarán a los de la biblioteca estándar.
Si sobrecarga al operator new
, siempre debe sobrecargar también la operator delete
correspondiente, incluso si nunca tiene la intención de llamarlo. La razón es que, si un constructor lanza durante la evaluación de una nueva expresión, el sistema de tiempo de ejecución devolverá la memoria al operator delete
coincidiendo con el operator new
que se llamó para asignar la memoria para crear el objeto. Si lo hace no proporciona una operator delete
coincidente, se llama al predeterminado, que casi siempre es incorrecto.
Si sobrecarga de new
y delete
, también debería considerar sobrecargar las variantes de la matriz.
Colocación new
C ++ permite que los operadores nuevos y eliminados tomen argumentos adicionales.
La llamada ubicación nueva le permite crear un objeto en una dirección determinada que se pasa a:
class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{
X* p = new(buffer) X(/*...*/);
// ...
p->~X(); // call destructor
}
La biblioteca estándar viene con las sobrecargas apropiadas de los operadores nuevos y de eliminación para esto:
void* operator new(std::size_t,void* p) throw(std::bad_alloc);
void operator delete(void* p,void*) throw();
void* operator new[](std::size_t,void* p) throw(std::bad_alloc);
void operator delete[](void* p,void*) throw();
Tenga en cuenta que, en el código de ejemplo para la ubicación nueva dada anteriormente, la operator delete
nunca se llama, a menos que el constructor de X emita una excepción.
También puede sobrecargar new
y delete
con otros argumentos. Al igual que con el argumento adicional para la ubicación nueva, estos argumentos también se enumeran entre paréntesis después de la palabra clave new
. Simplemente por razones históricas, estas variantes a menudo también se llaman ubicación nueva, incluso si sus argumentos no son para colocar un objeto en una dirección específica.
Clase específica nueva y eliminar
Lo más común es que desee ajustar la administración de la memoria porque la medición ha demostrado que las instancias de una clase específica o de un grupo de clases relacionadas se crean y destruyen a menudo y que la administración de la memoria predeterminada del sistema de tiempo de ejecución, optimizada para El desempeño general, trata ineficientemente en este caso específico. Para mejorar esto, puede sobrecargar nuevo y eliminar para una clase específica:
class my_class {
public:
// ...
void* operator new();
void operator delete(void*,std::size_t);
void* operator new[](size_t);
void operator delete[](void*,std::size_t);
// ...
};
Sobrecargado por lo tanto, nuevo y eliminar se comportan como funciones miembro estáticas. Para objetos de my_class
, el std::size_t
argumento siempre será sizeof(my_class)
. Sin embargo, estos operadores también son llamados para objetos asignados dinámicamente de clases derivadas , en cuyo caso podría ser mayor que eso.
Global nuevo y eliminar
Para sobrecargar lo nuevo global y eliminar, simplemente reemplace los operadores predefinidos de la biblioteca estándar con los nuestros. Sin embargo, esto rara vez debe hacerse.
La Decisión entre Miembro y No miembro.
Los operadores binarios =
(asignación), []
(suscripción de matriz), ->
(acceso de miembro), así como el operador n-ary ()
(llamada de función), siempre deben implementarse como funciones miembro , ya que la sintaxis de El lenguaje los requiere.
Otros operadores pueden implementarse como miembros o como no miembros. Sin embargo, algunos de ellos, por lo general, deben implementarse como funciones que no son miembros, porque usted no puede modificar su operando izquierdo. Los más prominentes de estos son los operadores de entrada y salida <<
y >>
, cuyos operandos de la izquierda son clases de flujo de la biblioteca estándar que no puede cambiar.
Para todos los operadores en los que tiene que elegir implementarlos como una función miembro o una función no miembro, use las siguientes reglas generales para decidir:
- Si es un operador unario , implementarlo como una función miembro .
- Si un operador binario trata ambos operandos por igual (los deja sin cambios), implemente este operador como una función no miembro .
- Si un operador binario no trata ambos operandos por igual (por lo general, cambiará su operando izquierdo), puede ser útil convertirlo en una función miembro del tipo de su operando izquierdo, si tiene que acceder a las partes privadas del operando.
Por supuesto, como con todas las reglas generales, hay excepciones. Si tienes un tipo
enum Month {Jan, Feb, ..., Nov, Dec}
y desea sobrecargar los operadores de incremento y decremento, no puede hacer esto como funciones miembro, ya que en C ++, los tipos de enumeración no pueden tener funciones miembro. Así que tienes que sobrecargarlo como una función gratuita. Y el operator<()
para una plantilla de clase anidada dentro de una plantilla de clase es mucho más fácil de escribir y leer cuando se realiza como una función miembro en línea en la definición de clase. Pero estas son excepciones raras.
(Sin embargo, si hace una excepción, no olvide el problema de la constancia para el operando que, para las funciones miembro, se convierte en el argumento implícito. Si el operador, como una función no miembro, tomaría su argumento más a la izquierda como una referencia const
, el mismo operador que una función miembro debe tener una const
al final para que *this
una referencia const
.)
Continuar a los operadores comunes para sobrecargar .
Digamos que tienes:
struct Foo
{
int a;
double b;
std::ostream& operator<<(std::ostream& out) const
{
return out << a << " " << b;
}
};
Dado que, no puedes usar:
Foo f = {10, 20.0};
std::cout << f;
Como operator<<
está sobrecargado como función miembro de Foo
, el LHS del operador debe ser un Foo
objeto. Lo que significa, se le pedirá que utilice:
Foo f = {10, 20.0};
f << std::cout
Lo cual es muy no intuitivo.
Si lo define como una función no miembro,
struct Foo
{
int a;
double b;
};
std::ostream& operator<<(std::ostream& out, Foo const& f)
{
return out << f.a << " " << f.b;
}
Podrás utilizar:
Foo f = {10, 20.0};
std::cout << f;
Lo cual es muy intuitivo.