Sobrecarga de operador

Iniciado por digimikeh, 15 Mayo 2019, 15:54 PM

0 Miembros y 1 Visitante están viendo este tema.

digimikeh

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?


Dungeons & dragons;
dragons.Attack();

CalgaryCorpus

Aqui mi perfil en LinkedIn, invitame un cafe aqui

@XSStringManolo

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.

K-YreX

Cita de: 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?

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 (cpp) [Seleccionar]

    // OPCION 1: Sobrecarga de <
    struct foo{
        // miembros
        bool operator<(const foo &f)const{/*...*/}
    };
    std::set<foo> my_set; // equivalente a std::set<foo, less<foo>, allocator<foo>> se necesita el operador para el criterio less<foo>

    // OPCION 2: Functor
    struct foo{
        // miembros
    };

    struct comparaFoo{
        bool operator()(const foo &f1, const foo &f2)const{/*...*/}
    };
    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
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

Loretz

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<.

digimikeh

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..
Dungeons & dragons;
dragons.Attack();

digimikeh

#6
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 (cpp) [Seleccionar]


#include "pch.h"
#include <iostream>
#include <set>

class Usuario {

char * nombre = nullptr;


public:
Usuario() : nombre(new char[16]) {}
~Usuario();

void SetNombre(const char * _nombre);


char * GetNombre() const {
return this->nombre;
}

friend bool operator<(const Usuario & _thisUser, const Usuario & _otherUser);


};

Usuario::~Usuario() {
delete[] nombre;
}

void Usuario::SetNombre(const char * _nombre) {
strcpy(this->nombre, _nombre);
}

bool operator<(const Usuario & _thisUser, const Usuario & _otherUser) {

for (int l = 0; l < 16; l++) {
if (_thisUser.nombre[l] < _otherUser.nombre[l]) {
return true;
}
else {

if (_thisUser.nombre[l] == _otherUser.nombre[l])
continue;

else
return false;

}
}


}



std::set<Usuario> usuarios;

int main(){

char * _nombre = new char[16];

for (int x = 0; x <= 3; x++) {

system("cls");

std::cout << "Ingrese nombre: ";
std::cin >> _nombre;

std::cout << std::endl;

Usuario usuario;
usuario.SetNombre(_nombre);

usuarios.insert(usuario);   //ciclo 0 lo realiza bien
                                                      //al intentar guardar en el ciclo 1 se congela

}

delete[] _nombre;
_nombre = nullptr;

return 0;

}


He optado por set<> porque deseo ingresar usuarios y no deben repetirse...
Estoy casi seguro que no he sobrecargado correctamente el operador..
Dungeons & dragons;
dragons.Attack();

K-YreX

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 (cpp) [Seleccionar]

bool Usuario::operator<(const Usuario &otro)const{
    return strncmp(this->nombre, otro.nombre, size) < 0;
}

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.
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

Loretz

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:
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 (cpp) [Seleccionar]

       Usuario usuario;  ///< creas un Usuario
       // etcetera,,,
       usuarios.insert(usuario);   ///< insertas una copia de tu Usuario en el set.


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:
Usuario::~Usuario() {
   std::cout << "delete[] " << nombre << '\n';
   //delete[] nombre;
}


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




digimikeh

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!
Dungeons & dragons;
dragons.Attack();