Pregunta ¿Cuáles son las reglas y expresiones idiomáticas básicas para la sobrecarga del operador?


Nota: Las respuestas fueron dadas en una orden específica, pero dado que muchos usuarios clasifican las respuestas según los votos, en lugar del tiempo que se les dio, aquí hay una índice de las respuestas en el orden en que tienen más sentido:

(Nota: Esto está destinado a ser una entrada a Preguntas frecuentes sobre C ++ de Stack Overflow. Si desea criticar la idea de proporcionar una pregunta frecuente en este formulario, entonces la publicación en meta que comenzó todo esto sería el lugar para hacer eso. Las respuestas a esa pregunta son monitoreadas en Sala de chat C ++, donde la idea de las preguntas frecuentes comenzó en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos a quienes se les ocurrió la idea).  


1841
2017-12-12 12:44


origen


Respuestas:


Operadores comunes para sobrecargar

La mayor parte del trabajo en operadores de sobrecarga es el código de la placa de la caldera. Eso no es de extrañar, ya que los operadores son meramente azúcar sintáctica, su trabajo real podría ser realizado por (y con frecuencia se reenví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 los usuarios no se compilará o el código de los usuarios se comportará de manera sorprendente.

Operador de Asignación

Hay mucho que decir sobre la asignación. Sin embargo, la mayor parte ya se ha dicho en Preguntas frecuentes sobre Copiar y Cambiar de GMan, así que omitiré la mayoría aquí, solo enumerando el operador de asignación perfecto para referencia:

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

Operadores de Bitshift (utilizados para E / S de Stream)

Los operadores de cambio de bits << y >>, aunque todavía se utilizan en las interfaces 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 sobrecargado en la mayoría de las aplicaciones. Para la sobrecarga de guía como operadores de manipulación de bits, consulte la sección a continuación sobre operadores binarios aritméticos. 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 comúnmente sobrecargados, son operadores de infijo binarios cuya sintaxis no especifica ninguna restricción sobre si deben ser miembros o no miembros. Como cambian su argumento de la izquierda (alteran el estado de la secuencia), deberían, de acuerdo con las reglas generales, implementarse como miembros del tipo de operando 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 salida y entrada de flujo definidos por la biblioteca estándar están definidos como miembros de las clases de flujo, cuando implementa operaciones de salida e ingreso para sus propios tipos, no puede 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 miembro. Las formas canónicas de los dos son estos:

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 operator>>, el ajuste manual del estado de la secuencia solo es necesario cuando la lectura en sí misma tuvo éxito, pero el resultado no es el esperado.

Operador de llamada de función

El operador de llamada de función, utilizado para crear objetos de función, también conocidos como funtores, debe definirse como un miembro función, por lo que siempre tiene el implícito this argumento de funciones miembro Aparte de esto, puede sobrecargarse para tomar cualquier cantidad de argumentos adicionales, incluido 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 toda 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 de copiar. Si un objeto de función necesita absolutamente usar 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 infijo binarios deberían, de acuerdo con las reglas generales, implementarse como funciones no miembro1. La negación del prefijo unario ! debería (de acuerdo con las mismas reglas) implementarse como una función miembro. (pero generalmente no es una buena idea sobrecargarlo).

Los algoritmos de la biblioteca estándar (p. std::sort()) y tipos (p. std::map) siempre esperará operator< Ser presente. sin embargo, el los usuarios de su tipo esperarán que todos los demás operadores estén presentes, también, así que si defines operator<, asegúrese de seguir la tercera regla fundamental de sobrecarga del operador y también defina todos los otros operadores de comparación booleanos. La forma canónica de implementarlos 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 solo están reenviando 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 lo és muy es poco probable que encuentre un caso de uso razonable para estos2.

1  Al igual que con todas las reglas generales, a veces puede haber razones para romper este 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, necesita ser const, también. Entonces, un operador de comparación implementado como una función miembro debería tener esta firma:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(Nota la const al final.)

2  Cabe señalar que la versión incorporada de || y && usa semántica de acceso directo. Mientras que los definidos por el usuario (porque son azúcar sintáctico para llamadas de método) no usan semántica de acceso directo. El usuario esperará que estos operadores tengan 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 unarios

Los operadores de incremento y decremento unarios vienen tanto en el prefijo como en el sabor postfix. Para distinguir uno de otro, las variantes de postfix toman un argumento int ficticio 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

La sobrecarga de unary menos y más no es muy común y probablemente sea mejor evitarla. Si es necesario, probablemente deberían estar sobrecargados como funciones miembro.

2  También tenga en cuenta que la variante de postfijo 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 postfijo. 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 acostumbraste a hacer i++, se vuelve muy difícil recordar hacer ++i en cambio cuando i no es de tipo incorporado (además, tendría que cambiar el código al cambiar un tipo), por lo que es mejor usar el hábito de usar siempre el incremento de prefijo, a menos que se requiera explícitamente postfix.

Operadores aritméticos binarios

Para los operadores aritméticos binarios, no olvide obedecer la tercera sobrecarga del operador de la regla básica: si proporciona +, también provee +=, si usted proporciona -no omitas -=, etc. Se dice que Andrew Koenig fue el primero en observar que los operadores de asignación de compuestos pueden usarse como base para sus homólogos no compuestos. Es decir, operador + se implementa en términos de +=, - se implementa en términos de -= etc.

De acuerdo con nuestras reglas generales, + y sus compañeros deben ser no miembros, mientras que sus contrapartes de asignación compuesta (+= etc.), cambiando su argumento de la izquierda, debe ser un miembro. Aquí está el código ejemplar para += y +, los otros operadores aritméticos binarios deberían 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 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 de operator+, no hay forma de evitar la copia. Cuando escribes a + b, esperas que el resultado sea un nuevo valor, que es por qué operator+ tiene que devolver un nuevo valor.3 También tenga en cuenta que 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 por la cual operator= tomando su argumento por copia.

Los operadores de manipulación de bits ~  &  |  ^  <<  >> debe implementarse de la misma manera que los operadores aritméticos. Sin embargo, (a excepción de la sobrecarga << y >> para salida y entrada) hay muy pocos casos de uso razonables para sobrecargar estos.

3  Una vez más, la lección que debe tomarse de esto es que a += b es, en general, más eficiente que a + b y debe ser preferido si es posible.

Array Subscripting

El operador de subíndice de matriz es un operador binario que debe implementarse como miembro de clase. Se utiliza para tipos similares a contenedores 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 operator[] (en cuyo caso puede omitir la variante no const), siempre debe proporcionar ambas variantes del operador.

Si value_type se refiere a un tipo incorporado, la variante const del operador debería devolver una copia en lugar de una referencia constante.

Operadores para tipos similares a punteros

Para definir sus propios iteradores o punteros inteligentes, debe sobrecargar el operador de desreferencia de prefijos unarios * y el operador de acceso de miembro de puntero 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, casi siempre, necesitarán tanto una versión const como una versión sin const. Para el -> operador, si value_type es de class (o struct o union) tipo, otro operator->() se llama recursivamente, hasta que operator->() devuelve un valor de tipo no de clase.

El operador de dirección única no debe estar sobrecargado.

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


Continuar a Operadores de conversión


896
2017-12-12 12:47



Las tres reglas básicas de la sobrecarga del operador en C ++

Cuando se trata de la sobrecarga del operador en C ++, hay tres reglas básicas que debes seguir. Al igual que con todas esas reglas, existen excepciones. A 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. Por lo menos, 99 de cada 100 de esas desviaciones que he visto no estaban justificadas. Sin embargo, podría haber sido 999 de 1000. Por lo tanto, será mejor que se apegue a las siguientes reglas.

  1. Siempre que el significado de un operador no sea obviamente claro e indiscutible, no debe sobrecargarse.  En cambio, proporcione una función con un nombre bien elegido.
    Básicamente, la primera y más importante regla para los operadores que sobrecargan, en su corazón, dice: No lo hagas. Eso puede parecer extraño, porque hay mucho que se sabe acerca de la sobrecarga del operador, por lo que muchos artículos, capítulos de libros y otros textos tratan de todo esto. Pero a pesar de esta evidencia aparentemente obvia, solo sorprendentemente son pocos los casos en que la sobrecarga del operador 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 y no controvertido. Contrariamente a la creencia popular, esto casi nunca es el caso.

  2. Siempre apegue a la semántica conocida del operador.
    C ++ no tiene limitaciones en la semántica de los operadores sobrecargados. Su compilador aceptará felizmente el código que implementa el binario + operador para restar de su operando derecho. Sin embargo, los usuarios de dicho operador nunca sospecharían de la expresión a + b restar a de b. Por supuesto, esto supone que la semántica del operador en el dominio de la aplicación es indiscutible.

  3. Siempre proporcione todo de un conjunto de operaciones relacionadas.
    Los operadores están relacionados entre síy a otras operaciones. Si tu tipo admite a + b, los usuarios esperarán poder llamar a += b, también. Si es compatible con el incremento de prefijo ++aellos esperarán a++ para trabajar también Si pueden verificar si a < b, ciertamente esperarán también poder verificar si a > b. Si pueden copiar y construir su tipo, esperan que la asignación también funcione.


Continuar a La Decisión entre Miembro y No miembro.


440
2017-12-12 12:45



La sintaxis general de la sobrecarga del operador en C ++

No puede cambiar el significado de los operadores para los tipos incorporados en C ++, los operadores solo pueden sobrecargarse para los tipos definidos por el usuario1. 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 cierto conjunto de parámetros solo una vez.

No todos los operadores pueden estar sobrecargados en C ++. Entre los operadores que no pueden sobrecargarse están: .  ::  sizeof  typeid  .* y el único operador ternario en C ++, ?: 

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

  • operadores aritméticos: +  -  *  /  % y +=  -=  *=  /=  %= (todo el infijo binario); +  - (prefijo unario); ++  -- (prefijo único y postfijo)
  • manipulación de bits: &  |  ^  <<  >> y &=  |=  ^=  <<=  >>= (todo el infijo binario); ~ (prefijo único)
  • álgebra de Boole: ==  !=  <  >  <=  >=  ||  && (todo el infijo binario); ! (prefijo único)
  • gestión de la memoria: new  new[]  delete  delete[]
  • operadores de conversión implícita
  • miscelánea: =  []  ->  ->*  ,  (todo el infijo binario); *  & (todo el prefijo unario) () (llamada de función, infijo n-ary)

Sin embargo, el hecho de que poder sobrecargar todos estos no significa que debería hazlo. Ver las reglas básicas de sobrecarga del operador.

En C ++, los operadores están sobrecargados en forma de funciones con nombres especiales. Al igual que con otras funciones, los operadores sobrecargados pueden implementarse generalmente como función miembro del tipo de su operando izquierdo o como funciones no miembro. Si usted es libre de elegir o está obligado a usar cualquiera de ellos, 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 infijo binario @, aplicado a los objetos x y y, se llama ya sea como operator@(x,y) o como x.operator@(y).4 

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

1  El término "definido por el usuario" podría 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 struct, class, union y enum, incluidos los de la biblioteca estándar, aunque no estén definidos como tales por los usuarios.

2  Esto está cubierto en una parte posterior de estas preguntas frecuentes

3  los @ no es un operador válido en C ++ y por eso lo uso como marcador de posición.

4  El único operador ternario en C ++ no puede sobrecargarse y el único operador n-ary siempre debe implementarse como una función miembro.


Continuar a Las tres reglas básicas de la sobrecarga del operador en C ++.


229
2017-12-12 12:46



La Decisión entre Miembro y No miembro

Los operadores binarios = (asignación), [] (matriz de suscripción), -> (acceso de miembros), así como el n-ario ()(función de llamada) operador, siempre debe implementarse como funciones miembro, porque la sintaxis del lenguaje lo requiere.

Otros operadores pueden implementarse como miembros o como no miembros. Algunos de ellos, sin embargo, generalmente deben implementarse como funciones que no son miembro, ya que su operando izquierdo no puede ser modificado por usted. El más destacado de estos son los operadores de entrada y salida << y >>, cuyos operandos izquierdos son clases de flujo de la biblioteca estándar que no puede cambiar.

Para todos los operadores donde tiene que elegir implementarlos como una función miembro o una función no miembro, use las siguientes reglas generales para decidir:

  1. Si es un operador unario, implementarlo como un miembro función.
  2. Si un operador binario trata ambos operandos por igual (no los modifica), implemente este operador como no es miembro función.
  3. Si un operador binario no no tratar ambos operandos Igualmente (por lo general, cambiará su operando de la izquierda), podría ser útil convertirlo en un miembro función de su tipo de 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 para ello, no puede hacer esto como funciones miembro, ya que en C ++, los tipos enum no pueden tener funciones miembro. Entonces debes sobrecargarlo como una función gratuita. Y operator<() para una plantilla de clase anidada dentro de una plantilla de clase es mucho más fácil escribir y leer cuando se hace como una función miembro en línea en la definición de la clase. Pero estas son raras excepciones.

(Sin embargo, Si haces una excepción, no olvides el problema const-ness para el operando que, para las funciones de miembro, se convierte en implícito this argumento. Si el operador como una función no miembro tomaría su argumento de la izquierda como una const referencia, el mismo operador que una función miembro necesita tener un const al final para hacer *this un const referencia.)


Continuar a Operadores comunes para sobrecargar.


211
2017-12-12 12:49



Operadores de conversión (también conocidos como Conversiones definidas por el usuario)

En C ++ puedes crear operadores de conversión, operadores que permiten al compilador convertir tus tipos y otros tipos definidos. Hay dos tipos de operadores de conversión, implícitos y explícitos.

Operadores de conversión implícita (C ++ 98 / C ++ 03 y C ++ 11)

Un operador de conversión implícito permite al compilador convertir implícitamente (como la conversión entre int y long) el valor de un tipo definido por el usuario para 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 intente hacer corresponder una llamada a 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 que lo haga. En el siguiente código, void f(const char*)será llamado porque my_string() no es un lvalue, entonces 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 elige una sobrecarga que no sospechaban. 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 entrarán en funcionamiento cuando usted no lo espere. 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_;
};

Observe la explicit. Ahora cuando intentas ejecutar el código inesperado de los operadores de conversión implícitos, obtienes un error de compilación:

prog.cpp: en la función 'int main ()':
prog.cpp: 15: 18: error: no hay función de coincidencia para la llamada a 'f (my_string)'
prog.cpp: 15: 18: nota: los candidatos son:
prog.cpp: 11: 10: nota: void f (my_string &)
prog.cpp: 11: 10: nota: ninguna conversión conocida para el argumento 1 de 'my_string' a 'my_string &'
prog.cpp: 12: 10: nota: void f (const char *)
prog.cpp: 12: 10: nota: ninguna conversión conocida para el argumento 1 de 'my_string' a 'const char *'

Para invocar al operador de conversión explícito, debes usar static_cast, un elenco de estilo C o un elenco de estilo de constructor (es decir, T(value) )

Sin embargo, hay una excepción a esto: el compilador puede convertir implícitamente a bool. Además, el compilador no puede hacer otra conversión implícita después de que se convierta en bool (un compilador puede hacer 2 conversiones implícitas a la vez, pero solo 1 conversión definida por el usuario al máximo).

Porque el compilador no lanzará "pasado" bool, los operadores de conversión explícitos ahora eliminan la necesidad del Lenguaje de Safe Bool. Por ejemplo, los punteros inteligentes antes de C ++ 11 usaban el modismo 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 convertir explícitamente un tipo a bool.

Continuar a Sobrecarga new y delete.


143
2018-05-17 18:32



Sobrecarga new y delete

Nota: Esto solo trata con el sintaxis de sobrecarga new y delete, no con el implementación de tales operadores sobrecargados. Creo que la semántica de la sobrecarga new y delete merecen sus propias preguntas frecuentes, dentro del tema de la sobrecarga de operadores, nunca puedo hacerle justicia.

Lo esencial

En C ++, cuando escribes un nueva expresión me gusta new T(arg) dos cosas suceden cuando se evalúa esta expresión: primero operator new se invoca para obtener la memoria bruta, y luego el constructor apropiado de T se invoca para convertir esta memoria bruta en un objeto válido. Del mismo modo, cuando elimina un objeto, primero se llama a su destructor y luego la memoria se devuelve a operator delete.
C ++ le permite sintonizar ambas operaciones: gestión de la memoria y construcción / destrucción del objeto en la memoria asignada. Esto último se hace escribiendo constructores y destructores para una clase. La gestión de la memoria de ajuste se hace escribiendo su propio operator new y operator delete.

La primera de las reglas básicas de sobrecarga de operador - no lo hagas - se aplica especialmente a la sobrecarga new y delete. Casi las únicas razones para sobrecargar estos operadores son problemas de rendimiento y restricciones de memoria, y en muchos casos, otras acciones, como cambios a los algoritmos utilizado, proporcionará una gran cantidad de mayor relación costo / ganancia que intentar modificar la gestión de la memoria.

La biblioteca estándar de C ++ viene con un conjunto predefinido new y delete operadores. 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(); 

Las dos primeras asignan / desasignan memoria para un objeto, las dos últimas para una matriz de objetos. Si proporciona sus propias versiones de estos, lo harán no sobrecargar, sino reemplazar los de la biblioteca estándar.
Si sobrecargas operator new, siempre debes sobrecargar el emparejamiento operator delete, 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 en tiempo de ejecución devolverá la memoria al operator delete coincidiendo con el operator new que fue llamado para asignar la memoria para crear el objeto. Si no proporciona una coincidencia operator delete, se llama al predeterminado, que casi siempre es incorrecto.
Si sobrecargas new y delete, también debería considerar sobrecargar las variantes de matriz.

Colocación new

C ++ permite que los operadores nuevos y eliminados tomen argumentos adicionales.
La llamada colocació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 eliminados 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, operator delete nunca se llama, a menos que el constructor de X arroje una excepción.

También puedes 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 ubicaciones nuevas, incluso si sus argumentos no son para colocar un objeto en una dirección específica.

Clase específica de nuevo y eliminar

Lo más habitual es que desee ajustar la gestión de 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 gestión de memoria predeterminada del sistema de tiempo de ejecución, ajustado para el rendimiento general, se 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 dinámicamente asignados de clases derivadas, en cuyo caso podría ser mayor que eso.

Global nuevo y eliminar

Para sobrecargar el nuevo global y eliminar, simplemente reemplace los operadores predefinidos de la biblioteca estándar por el nuestro. Sin embargo, esto rara vez necesita hacerse.


130
2017-12-12 13:07



Por qué no puede operator<< función para transmitir objetos a std::cout o a un archivo ser una función miembro?

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;

Ya que operator<< está sobrecargado como una función miembro de Foo, el LHS del operador debe ser Foo objeto. Lo que significa que se le solicitará que use:

Foo f = {10, 20.0};
f << std::cout

que 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 usar:

Foo f = {10, 20.0};
std::cout << f;

que es muy intuitivo.


29
2018-01-22 19:00