Foro de elhacker.net

Programación => Programación C/C++ => Mensaje iniciado por: digimikeh en 15 Mayo 2019, 15:54 pm



Título: Sobrecarga de operador
Publicado por: digimikeh en 15 Mayo 2019, 15:54 pm
Hola amigos..

Estoy experimentando con el contenedor set y tengo duda con el operador de sobrecarga que se necesita para meter al contenedor un tipo creado por mi mismo...

Según estuve leyendo y alguien me dijo también que necesito sobrecargar el operador <

Pero este operador menor que, en que forma hace la comparación de elementos?, el contenedor set no permite objetos duplicados, no seria mas lógico sobrecargar == en vez de < para comparar si el elemento ya existe?




Título: Re: Sobrecarga de operador
Publicado por: CalgaryCorpus en 15 Mayo 2019, 16:15 pm
Sobrecarga <<
No <


Título: Re: Sobrecarga de operador
Publicado por: @XSStringManolo en 15 Mayo 2019, 18:42 pm
https://en.cppreference.com/w/cpp/language/operators
Aqui tienes todos los operadores explicados y con ejemplos.

Para comparar contenedores del mismo tipo y longitud se sobrecarga el operador == los elementos dentro de los contenedores del mismo tipo tambien deben ser elementos del mismo tipo.

La sobrecarga del operador << es para imprimir en pantalla los elementos del set.


Título: Re: Sobrecarga de operador
Publicado por: K-YreX en 15 Mayo 2019, 19:42 pm
Hola amigos..

Estoy experimentando con el contenedor set y tengo duda con el operador de sobrecarga que se necesita para meter al contenedor un tipo creado por mi mismo...

Según estuve leyendo y alguien me dijo también que necesito sobrecargar el operador <

Pero este operador menor que, en que forma hace la comparación de elementos?, el contenedor set no permite objetos duplicados, no seria mas lógico sobrecargar == en vez de < para comparar si el elemento ya existe?

Creo que a lo que te refieres es al tipo de ordenación interno del <set>. Un <set> es un conjunto de valores no repetidos y ordenados según un criterio que puedes elegir tú (criterio por defecto: less<T>). Es por esto que si creas un <set> para almacenar objetos deben poder ordenarse de menor a mayor y para ello tienes dos opciones:
  • Sobrecargar el operador <.
  • Crear un functor. (Verás que se hablan mucho de functores para <set> y <priority_queue>.
Código
  1. // OPCION 1: Sobrecarga de <
  2. struct foo{
  3.    // miembros
  4.    bool operator<(const foo &f)const{/*...*/}
  5. };
  6. std::set<foo> my_set; // equivalente a std::set<foo, less<foo>, allocator<foo>> se necesita el operador para el criterio less<foo>
  7.  
  8. // OPCION 2: Functor
  9. struct foo{
  10.    // miembros
  11. };
  12.  
  13. struct comparaFoo{
  14.    bool operator()(const foo &f1, const foo &f2)const{/*...*/}
  15. };
  16. std::set<foo, comparaFoo> my_set; // cambiamos el criterio por defecto por el nuestro

Si quieres saber más sobre esto: http://www.cplusplus.com/reference/set/set/
Puedes leer la teoría. Es muy concisa pero precisa, creo que está muy bien para entenderlo (aunque en inglés, pero es un inglés sencillo). Suerte :-X


Título: Re: Sobrecarga de operador
Publicado por: Loretz en 15 Mayo 2019, 20:12 pm
Sí, así es. Necesitas sobrecargar el "operator<"; también puedes usar una función objeto (function object) que defina cuando uno de esos tipos es "menor que" el otro, y también puedes crear el set usando una expresión lambda como función de comparación. Las tres son funcionalmente equivalentes.

Un std::set contiene un conjunto ordenado de claves únicas, es por eso que necesitas una función que sirva para las dos cosas, definir el orden y garantizar unicidad. El operator< define un "strict weak ordering" (googlear) que es lo que necesita un std::set para establecer orden y unicidad, que se obtiene a partir de la relación de equivalencia (que no la igualdad), obtenida a partir del mismo operator<.


Título: Re: Sobrecarga de operador
Publicado por: digimikeh en 16 Mayo 2019, 18:15 pm
Gracias por las respuestas...

Muy bien, creo que tomare todas las opciones, tanto para comparar como para imprimir la clase y determinar el orden...

< , << y ==

Y gracias también por los enlaces..


Título: Re: Sobrecarga de operador
Publicado por: digimikeh en 17 Mayo 2019, 04:20 am
Rayos cósmicos... he intentado sobrecargar al menos el operador < .. pero algo no anda bien..  esta vez compila, pero no se comporta como esperaba

Código
  1.  
  2. #include "pch.h"
  3. #include <iostream>
  4. #include <set>
  5.  
  6. class Usuario {
  7.  
  8. char * nombre = nullptr;
  9.  
  10.  
  11. public:
  12. Usuario() : nombre(new char[16]) {}
  13. ~Usuario();
  14.  
  15. void SetNombre(const char * _nombre);
  16.  
  17.  
  18. char * GetNombre() const {
  19. return this->nombre;
  20. }
  21.  
  22. friend bool operator<(const Usuario & _thisUser, const Usuario & _otherUser);
  23.  
  24.  
  25. };
  26.  
  27. Usuario::~Usuario() {
  28. delete[] nombre;
  29. }
  30.  
  31. void Usuario::SetNombre(const char * _nombre) {
  32. strcpy(this->nombre, _nombre);
  33. }
  34.  
  35. bool operator<(const Usuario & _thisUser, const Usuario & _otherUser) {
  36.  
  37. for (int l = 0; l < 16; l++) {
  38. if (_thisUser.nombre[l] < _otherUser.nombre[l]) {
  39. return true;
  40. }
  41. else {
  42.  
  43. if (_thisUser.nombre[l] == _otherUser.nombre[l])
  44. continue;
  45.  
  46. else
  47. return false;
  48.  
  49. }
  50. }
  51.  
  52.  
  53. }
  54.  
  55.  
  56.  
  57. std::set<Usuario> usuarios;
  58.  
  59. int main(){
  60.  
  61. char * _nombre = new char[16];
  62.  
  63. for (int x = 0; x <= 3; x++) {
  64.  
  65. system("cls");
  66.  
  67. std::cout << "Ingrese nombre: ";
  68. std::cin >> _nombre;
  69.  
  70. std::cout << std::endl;
  71.  
  72. Usuario usuario;
  73. usuario.SetNombre(_nombre);
  74.  
  75. usuarios.insert(usuario);   //ciclo 0 lo realiza bien
  76.                                                      //al intentar guardar en el ciclo 1 se congela
  77.  
  78. }
  79.  
  80. delete[] _nombre;
  81. _nombre = nullptr;
  82.  
  83. return 0;
  84.  
  85. }
  86.  

He optado por set<> porque deseo ingresar usuarios y no deben repetirse...
Estoy casi seguro que no he sobrecargado correctamente el operador..


Título: Re: Sobrecarga de operador
Publicado por: K-YreX en 17 Mayo 2019, 04:46 am
Bueno el problema principal como bien dices es que el operador < no está bien sobrecargado. Este operador pertenece a la clase, no es un operador externo a la clase que deba declararse como <friend>, es decir, sería así:
Código
  1. bool Usuario::operator<(const Usuario &otro)const{
  2.    return strncmp(this->nombre, otro.nombre, size) < 0;
  3. }
Esa función compara el número de caracteres indicados en <size> de ambas cadenas y retorna un valor negativo si la primera es menor a la segunda, un valor positivo si la primera es mayor a la segunda y 0 si son iguales.

Además de eso la declaración de <usuario> la puedes hacer una única vez antes de empezar el bucle y luego ir cambiando el valor de <nombre> en cada iteración.

Además no tiene mucho sentido usar arrays dinámicos si siempre los creas de tamaño 16, porque entonces... hasta dónde comparas las cadenas?? Tienes dos opciones:
  • Crear un array estático muy grande donde guardar el nombre introducido por el usuario, calcular los caracteres útiles y entonces reservar memoria justo para esos caracteres.
  • Usar string que es la ventaja de C++ para trabajar con cadenas de caracteres. Si usas string tendrás que convertirlos a char* para usar las funciones típicas de las cadenas pero eso se puede hacer simplemente con <nombre_string.c_str()>

Aparte de eso, el <set> mejor que sea local en el <main> en lugar de global.


Título: Re: Sobrecarga de operador
Publicado por: Loretz en 17 Mayo 2019, 08:50 am
Primero voy a hacer algunos comentarios morales:

Estás aprendiendo C++, entonces:

0) necesitas un buen libro.

1) no uses new (y no uses delete). Son mecanismos para situaciones excepcionales. En la vida real, cuando un experto los usa, debe dar explicaciones, convincentes.

2) no uses system(), menos en algo como system("cls"); Si estás intentando aprender el lenguaje, ¿cómo es que ya has aprendido los vicios de un mal programador de hace 30 años?

3) no uses std::endl;  no lo necesitas. Quizá, si en algún momento te encuentras en las fronteras de alguna sofisticada arquitectura concurrente, bueno, en ese momento supongo que ya sabrás. Mientras tanto sólo molesta.

4) no uses strcpy(), estás llamando a la desgracia. Pero de todos modos, así como en C++ no se usan punteros tampoco se usan arrays, así que obviamente strcpy no tiene ninguna razón de ser.

Y un comentario técnico:

Tu programa tiene dos errores:

1) La función de comparación (tu operator<()) no tiene en cuenta cuando las dos cadenas son iguales. Puedes probar con:
Código:
bool operator<(const Usuario& _thisUser, const Usuario& _otherUser) {
    return strcmp(_thisUser.nombre, _otherUser.nombre) < 0;
}



2) estás usando delete dos veces sobre el mismo puntero.

En esta porción del
Código
  1.        Usuario usuario;  ///< creas un Usuario
  2.        // etcetera,,,
  3.        usuarios.insert(usuario);   ///< insertas una copia de tu Usuario en el set.
  4.  

El problema es que usuarios.insert() inserta una copia, y como no has definido qué es hacer una copia de un Usuario, el compilador ha sintetizado tanto el "copy constructor" como el "copy assignment operator", y en ese caso hace una "shallow copy" del puntero nombre.

Luego se está invocando delete[] dos veces sobre cada uno de los punteros. La primera vez cuando la variable local usuario sale de ámbito y la segunda vez cuando el set usuarios sale de ámbito (se invoca el destructor de sus elementos).
Puedes reemplazar tu destructor por este otro para ver que se está invocando dos veces delete[] para cada nombre:
Código:
Usuario::~Usuario() {
    std::cout << "delete[] " << nombre << '\n';
    //delete[] nombre;
}

Lectura:
https://en.cppreference.com/w/cpp/language/rule_of_three (https://en.cppreference.com/w/cpp/language/rule_of_three) The rule of three/five/zero





Título: Re: Sobrecarga de operador
Publicado por: digimikeh en 18 Mayo 2019, 02:21 am
Gracias por las respuestas..

me queda claro todo, lo único si que no comprendí bien el por qué de dos cosas

por qué dices que no debo usar system("cls"); de que otra forma puedo limpiar pantalla?..  (conozco otro método que es insertar 100 lineas vacías pero esto haría que el contenido se muestre abajo)

y lo otro, es el std::endl;  la otra forma que conozco es con la secuencia de escape \n

Saludos!


Título: Re: Sobrecarga de operador
Publicado por: RayR en 18 Mayo 2019, 03:39 am
Gracias por las respuestas..

me queda claro todo, lo único si que no comprendí bien el por qué de dos cosas

por qué dices que no debo usar system("cls"); de que otra forma puedo limpiar pantalla?..  (conozco otro método que es insertar 100 lineas vacías pero esto haría que el contenido se muestre abajo)

y lo otro, es el std::endl;  la otra forma que conozco es con la secuencia de escape \n

Saludos!

Aunque no fui yo quien te mencionó esos dos puntos, aquí te comento algunas cosas.

Deberías evitar llamar a system(), ya que lo que sucede cuando la invocas no es simplemente que estés llamando a una función (como pasa con printf, scanf, etc.) sino que creas un proceso nuevo, en el cual ejecutas un programa distinto al tuyo, y todo eso es bastante pesado, y casi nunca se justifica. Es una práctica bastante mala.

Lamentablemente no hay una forma estándar de limpiar la pantalla. Insertar líneas es una solución pobre y, como dices, no te deja el cursor al inicio (a menos que en tu programa lleves todo el tiempo un seguimiento de dónde está el cursor). La forma de hacerlo es específica de cada sistema operativo. En Windows es una manera bastante fea y requiere varias líneas de código, pero es lo que hay. Existen bibliotecas/librerías multiplataforma, como ncurses, pero para algo tan simple, no le veo mucho sentido a usarlas. En casos así, yo siempre recurro a las APIs de cada SO, que además suelen ser más eficientes y dar menos problemas.

En cuanto a std::endl, no hay nada de malo en que la uses, salvo que estás tecleando de más sin necesidad. Es común leer que, dado que endl provoca que además de imprimir una nueva línea, se limpie el buffer (lo que ocasiona que se escriban/muestren inmediatamente los datos) es menos eficiente que '\n' (que sólo imprime una nueva línea, sin limpiar el buffer, por lo que los datos podrían no imprimirse en ese momento) pero a cambio garantiza que los datos se muestren correctamente, cuando deben. Todo eso aplica cuando estamos escribiendo a un archivo, pero cuando estamos usando la salida estándar stdout (la consola/terminal/línea de comandos), como en el caso de cout, no hay ninguna diferencia. En Linux, de forma predeterminada stdout es "line buffered", es decir, el buffer se limpia automáticamente cuando se encuentra un caracter de nueva línea, así que ambos, endl y '\n', tienen el mismo efecto. En Windows stdout ni siquiera usa buffer (por defecto) así que imprimas lo que imprimas, se mostrará tan pronto como sea posible. De nuevo, es irrelevante si usas endl o '\n'.