Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   Sobrecarga de operadores de clase (https://www.clubdelphi.com/foros/showthread.php?t=91190)

AgustinOrtu 29-11-2016 04:57:45

Sobrecarga de operadores de clase
 
Pues eso, aunque se supone que Delphi no soporta sobrecargar los operadores de clase como si sucede con los tipos records o registros, existe cierta sintaxis que lo hace posible, apartentemente no documentada ni oficial

En fin, no se como lo habra descubierto el que lo hizo, o de donde lo saco. La cuestion es que ha publicado dos fragmentos de codigo en GitHub, aca y aca

Sorprendentemente este codigo compila, funciona y no hay fugas de memoria, en Delphi 2010 y Delphi 10.1 Berlin, asi que supongo que "la compatibilidad se mantiene" en todas las versiones del medio.

Código Delphi [-]
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

type
  TStringListHelper = class helper for TStringList
  public
    class function &&op_In(const Value: string; Target: TStringList): Boolean; static;
    class function &&op_LogicalOr(A, B: TStringList): TStringList; static;
  end;

class function TStringListHelper.&&op_In(const Value: string; Target: TStringList): Boolean;
begin
  Result := Target.IndexOf(Value) <> -1;
end;

class function TStringListHelper.&&op_LogicalOr(A, B: TStringList): TStringList;
begin
  if A <> nil then
    Result := A
  else
    Result := B;
end;

procedure Test;
var
  StringListA, StringListB, StringListC: TStringList;
begin
  StringListA := nil;
  StringListB := TStringList.Create;
  try
    StringListB.Add('Hola mundo');
    StringListC := StringListA or StringListB; // -> StringListC apunta al mismo objeto que StringListB
    Writeln('StringListB.Text: ' + StringListB.Text); // imprime hola mundo
    Writeln('StringListC.Text: ' + StringListC.Text); // imprime hola mundo
    Writeln(BoolToStr(StringListB = StringListC, True)); // imprime True
    Writeln(BoolToStr('Hola mundo' in StringListB, True)); // imprime True
    Writeln(BoolToStr('blabla' in StringListB, True)); // imprime False
  finally
    StringListB.Free;
  end;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  try
    Test;
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Me parece algo realmente poderoso y peligroso al mismo tiempo, no he podido ordenar mis ideas por el momento

Realmente algo como esto si que es util para escribir codigo mas elegante y sencillo. Muchas veces se emplea el uso de records como envolturas de clases o tipos primitivos ya que ellos son los unicos con los que se pueden sobrecargar los operadores; pero esto lo lleva a otro nivel

De hecho es posible hasta con genericos como se puede ver en el codigo del autor

jhonny 29-11-2016 06:13:16

Sí que sí, bastante interesante, me he preguntado desde hace tiempo ¿Cuál será la razón para que la sobrecarga de operadores no esté soportada de manera nativa en las clases?, pues como bien indicas esto es poderoso aunque a la vez peligroso.

¿Cómo habrá sabido este personaje que tenía que anteponer &&op_ al nombre de cada operador? :D

Bastante interesante, el truco y lo que habrá detrás de dicha conclusión.

P.D: Sólo anotar, que me parece muy curiosa la observación que hace un usuario diciendo que no funciona en Records :D

Al González 29-11-2016 07:30:50

Interesante, Agustín. Al estar depurando, con la ventana CPU, me parece haber visto nombres similares a esos. Quizá de ahí el descubridor obtuvo los nombres "reales" que les da el compilador a esos métodos.

Sólo los compiladores con ARC (Delphi para dispositivos móviles) admiten la sobrecarga "formal" de operadores en clases, ya que en ellos puede uno despreocuparse por las instancias "intermedias" que se crean a mitad de algunas operaciones, dado que todos los objetos usan un contador de referencias, tal como lo hacen los String en cualquiera de los compiladores de Delphi.

Creo que la explicación al truco podría estar en que sólo el revisor sintáctico del compilador impide la declaración en clases, pero el resto de la compilación se realiza asumiendo que ya se superaron las validaciones sintácticas de plataforma y procede sin restricciones (asumiendo también el nombre transformado del método).

Resulta tentador empezar a aprovechar esta puerta trasera en los compiladores de escritorio, pero ya aprendí la lección, y es mejor prescindir de características del compilador no oficiales. Además hay que tener cuidado no de aplicar ningún operador que deje un objeto "suelto" por ahí. :p

¡Qué hallazgo! :)

Neftali [Germán.Estévez] 29-11-2016 08:18:55

Cita:

Empezado por AgustinOrtu (Mensaje 511328)

Código Delphi [-]
   ...
    Writeln(BoolToStr('Hola mundo' in StringListB, True)); // imprime True
    Writeln(BoolToStr('blabla' in StringListB, True)); // imprime False
   ...

Este me ha gustado mucho.
^\||/^\||/^\||/^\||/^\||/

AgustinOrtu 29-11-2016 11:03:29

Marco Cantú ha confirmado que esto es una puerta trasera, que es una característica no intencionalmente puesta a nuestro alcance, tal y como comenta nuestro amigo Al González

Los operadores que crean objetos intermedios seguro que ocasionan fugas de memoria.

Aún así me parece un juguetito muy interesante y que tiene mucha tela por donde cortar.

Esta es la lista completa de operadores:

Cita:

class operator class function
Implicit &&op_Implicit
Explicit &&op_Explicit
Negative &&op_UnaryNegation
Positive &&op_UnaryPlus
Inc &&op_Increment
Dec &&op_Decrement
LogicalNot &&op_LogicalNot
Trunc &&op_Trunc
Round &&op_Round
In &&op_In
Equal &&op_Equality
NotEqual &&op_Inequality
GreaterThan &&op_GreaterThan
GreaterThanOrEqual &&op_GreaterThanOrEqual
LessThan &&op_LessThan
LessThanOrEqual &&op_LessThanOrEqual
Add &&op_Addition
Subtract &&op_Subtraction
Multiply &&op_Multiply
Divide &&op_Division
IntDivide &&op_IntDivide
Modulus &&op_Modulus
LeftShift &&op_LeftShift
RightShift &&op_RightShift
LogicalAnd &&op_LogicalAnd
LogicalOr &&op_LogicalOr
LogicalXor &&op_ExclusiveOr
BitwiseAnd &&op_BitwiseAnd
BitwiseOr &&op_BitwiseOr
BitwiseXor &&op_BitwiseXOR
Include &&op_Include
Exclude &&op_Exclude
A mi también me ha gustado mucho el operador in. Y el del or para usar un objeto u otro en caso de nil también me parece que tiene mucho juego

En fin, es cuestión de creatividad. Pero ya hemos sido advertidos: es posible que solucionen este defecto

escafandra 29-11-2016 18:04:00

Para mi siempre ha sido algo natural la sobrecarga de operadores que uso bastante, ahorrándome código y proporcionando limpieza e intuición al código. Estoy hablando de C++
Siempre lo he echado en falta en delphi.

Saludos.

AgustinOrtu 29-11-2016 18:43:47

En C++ o los lenguajes con recoleccion de basura es seguro tener operadores de clase.

En Delphi que no es necesariamente el caso, puede haber muchas fugas de memoria, y creo que ese es el motivo por el cual no tenemos a nuestra disposicion sobrecarga de operadores. Al menos no para el compilador para Windows; los que implementan ARC, como señalo Al, si que permiten la sobrecarga

Los record son un caso especial porque Delphi se encarga de manejar la memoria por nosotros y entonces no hay problema

A mi tambien siempre me hacian mucha falta en Delphi.. seria muy bueno poder tener esta sintaxis de manera oficial, por lo menos en interfaces que si implementan el mecanismo de contador de referencias

Otro detalle es que no necesariamente hay que usar un ayudante de clase como esta mas arriba. Es posible utilizar una subclase para lograr el mismo efecto; o tambien el viejo truco de la clase interpuesta

Código Delphi [-]
  TStringListEx = class(TStringList)
  public
    class function &&op_In(const Value: string; Target: TStringListEx): Boolean; static;
    class function &&op_LogicalOr(A, B: TStringListEx): TStringListEx; static;
  end;

Código Delphi [-]
  TStringList = class(Classes.TStringList)
  public
    class function &&op_In(const Value: string; Target: TStringList): Boolean; static;
    class function &&op_LogicalOr(A, B: TStringList): TStringList; static;
  end;

Al González 29-11-2016 19:12:12

Hablando de estas cosas, yo sueño con el día en que pueda escribir en Delphi "expresiones de asignación" como
Código Delphi [-]
...

If ... Then
  If (A := B) > 10 Then  // <---
    ...

...
en lugar de
Código Delphi [-]
...

If ... Then
  If TghSys.SetInt (A, B) > 10 Then  // <---
    ...

...
que suelo escribir en lugar de
Código Delphi [-]
...

If .. Then
Begin  // <---
  A := B;  // <---

  If A > 10 Then  // <---
    ...
End;  // <---

...

Un abrazo esperanzador. :)

Al González.

roman 29-11-2016 19:22:02

Cita:

Empezado por AgustinOrtu (Mensaje 511341)
En C++ o los lenguajes con recoleccion de basura es seguro tener operadores de clase.]

¿C++ tiene recolector de basura? :eek:

Y si no, ¿por qué en C++ sí es seguro y en Delphi no?

LineComment Saludos

roman 29-11-2016 19:24:35

Cita:

Empezado por Al González (Mensaje 511342)
yo sueño con el día en que pueda escribir en Delphi "expresiones de asignación" como
Código Delphi [-]
...

If ... Then
  If (A := B) > 10 Then  // <---
    ...

...

¡Anda! Ahora sí me sorprendiste. ¿Tú soñando con una característica típica del lenguaje C? :rolleyes:

LineComment Saludos

AgustinOrtu 29-11-2016 19:29:25

Ay ay me equivoque :)

El que recolecta basura era el C#

Casimiro Notevi 29-11-2016 19:29:28

Cita:

Empezado por roman (Mensaje 511344)
¡Anda! Ahora sí me sorprendiste. ¿Tú soñando con una característica típica del lenguaje C? :rolleyes:

Me lo has quitado de la boca... :)

Al González 29-11-2016 19:49:29

Cita:

Empezado por roman (Mensaje 511344)
¡Anda! Ahora sí me sorprendiste. ¿Tú soñando con una característica típica del lenguaje C? :rolleyes:

El lenguaje C tiene algunas cosas muy buenas, Román. ;)

roman 29-11-2016 20:08:07

Cita:

Empezado por Al González (Mensaje 511347)
El lenguaje C tiene algunas cosas muy buenas, Román. ;)

Eso lo sé, pero pensé que tú no :D

Pero ya en serio, me causa curiosidad el uso que deseas, quizá por no tener contexto. En lo particular, esa característica (que la asignación sea un operador) casi sólo la uso para ciclos en los que obtengo y uso un dato:

Código:

/*
  DameRegistro devuelve el siguiente registro o FALSE si no hay más registros
*/
WHILE registro = DameOtro() DO
BEGIN
  MuestraRegistro(registro)
END

En otros casos, puede devenir en una lectura confusa.

LineComment Saludos

roman 29-11-2016 20:13:51

Cita:

Empezado por AgustinOrtu (Mensaje 511345)
Ay ay me equivoque :)

El que recolecta basura era el C#

Ok. Pero entonces, ¿qué hace que C++ si pueda tener el operator overloading y delphi no?

LineComment Saludos

jhonny 01-12-2016 00:21:09

Un parentesis pequeño, sólo para mostrar el recolector de basuras de C#



P.D: Perdón, no pude evitarlo. :D

mamcx 01-12-2016 17:30:49

Cita:

Empezado por roman (Mensaje 511351)
Ok. Pero entonces, ¿qué hace que C++ si pueda tener el operator overloading y delphi no?

LineComment Saludos

QUe C++ es el frankestein de los lenguajes y le inyectan toda caracteristica bajo el sol. Prácticamente puedes programar como si estuvieras en otro lenguaje abusando de C++ como te de la gana.


Delphi, por el contrario, fue derivado de alguien que *diseño* el lenguaje:

http://pascal-central.com/ppl/#Origins

Cita:

Niklaus Wirth completed development of the original Pascal programming language in 1970. He based it upon the block structured style of the Algol programming language. There were two original goals for Pascal.

According to the Pascal Standard (ISO 7185), these goals were to a) make available a language suitable for teaching programming as a systematic discipline based on fundamental concepts clearly and naturally reflected by the language, and b) to define a language whose implementations could be both reliable and efficient on then-available computers.
Delphi/Pascal tiene un compilador veloz *PRECISAMENTE* por ser tan anti-C++ como es posible.

----

Ahora, lograr poder sobrecargar operadores no es en si algo tan problematico... excepto que en los lenguajes imperativos existe una división entre expresiones, clases, tipos, statements (bloques), etc. Si Delphi fuera un lenguaje unificado a todo es una expresión esto sería un cambio trivial.

Tambien *supongo* que no seria trivial el darle una disponibilidad universal y que efectos tendria con el chequeo e intersección de tipos y si quedaria oscurecido el código por su uso. Esto no lo he pensando asi que es pura especulación.
---

En ultimas hay cambios a los lenguajes que los alteran de forma fundamental. Intentar agregarles cosas puede terminar siendo poco "idiomático" o convertirse en una característica de nicho poco usada pero que igual tiene su coste en mantenimiento e implementación.


P.D: Esta es una caracteristica que no es tan problematica si esta bien implementada, y NO SE PERMITE redefinir o crear nuevos operadores al vuelo (un problema que termina creando diversos codigos con el mismo operador pero diferente semantica!). Si esta limitado a unos pocos es muy util!

escafandra 01-12-2016 19:21:38

¿Qué tendrá C/C++ que es tan envidiado y odiado?

Saludos.

Casimiro Notevi 01-12-2016 19:24:47

Cita:

Empezado por escafandra (Mensaje 511400)
¿Qué tendrá C/C++ que es tan envidiado y odiado?
Saludos.

C me gusta, C++ no me gusta :)

escafandra 01-12-2016 19:28:24

Cita:

Empezado por Casimiro Notevi (Mensaje 511401)
C me gusta, C++ no me gusta :)

Ya pero características propias de C++ son tan envidiadas cómo el propio C. No será tan malo, diho yo. :) A mi me gustan ambos y delphi también.

Saludos


La franja horaria es GMT +2. Ahora son las 08:52:45.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi