Posts tagged ‘references’

At Least Python Got Equality Right

2013-01-03 23:13

I’m still flabbergasted after going through the analysis of PHP == operator, posted by my infosec friend Gynvael Coldwind. Up until recently, I knew two things about PHP: (1) it tries to be weakly typed and (2) it is not a stellar example of language design. Now I can confidently assert a third one…

It’s completely insane.

However, pondering the specific case of equality checks, I realized it’s not actually uncommon for programming languages to confuse the heck out of developers with their single, double or even triple “equals”. Among the popular ones, it seems to be a rule rather than exception.

Just consider that:

  • JavaScript has both == and ===, exactly like PHP does. And the former is just slightly less crazy than its PHP counterpart. For both languages, it just seems like a weak typing failure.
  • In C and C++, you may easily use = (assignment) in lieu of == (equality), because the former is perfectly allowed inside conditions for if, while or for statements.
  • Java is famously counterintuitive when it comes to comparing strings, requiring to use String.equals method rather than == (like in case of other fundamental data types). Many, many programmers have been bitten by that. (The fact that under certain conditions you can compare strings char-by-char with == doesn’t exactly help either).
  • C# complicates stuff even more by allowing to override Equals and overload == operator. It also introduces ReferenceEquals which usually works like ==, except when the latter is overloaded. Oh, and it also has two different kinds of types (value and reference types) which by default compare in two different ways… Joy!

The list could likely go on and include most of the mainstream languages but one of them would be curiously absent: Python.

You see, Python got the == operator right:

  • It tests for equality only, not identity (also known as “reference equality”). For that there is a separate is operator.
  • All basic objects – not only strings, but also lists or dictionaries (hash tables) – compare by value. Hence e.g. two lists are equal if they contain equal elements in the same order, whether or not they are the same objects.
  • Implicit conversions are applied judiciously. Different types of numbers (int, long, float) compare to each other just fine, but there is clear distinction between 42 (number) and "42" (string).
  • You can overload == but there are no magical tricks that instantly turn your class into wannabe fundamental type (like in C#). If you really want value semantics, you need to write that yourself.

In retrospect, all of this looks like basic sanity. Getting it right two decades ago, however… That’s work of genius, streak of luck – or likely both.

Tags: , , , , ,
Author: Xion, posted under Programming » 1 comment

About Java references

2011-12-17 19:22

There is somewhat common misconception about garbage collecting, that it totally frees the programmer from memory-related concerns. Granted, it makes the task easier in great many cases, but it does so at the expense of significant loss of control over objects’ lifetime. Normally, they are kept around for at least until they are not needed anymore – and usually that’s fine for the typical definitions of “need” and “at least”. Usually – but not always.

For those less typical use cases, garbage-collected environments provide mechanisms allowing to regain some of that lost control, to the extent necessary for particular task. Java, for example, offers a variety of different types of references, enabling to change the notion of what it means for an object to be eligible for garbage collecting. Choosing the right one for a problem at hand can be crucial, especially if we are concerned with the memory footprint of our application. Since – as the proverb goes – JVM expands to fill all available memory, it’s good to know about techniques which help maintain our heap size in check.

The default is strong

So today, I will discuss the SoftReference and WeakReference classes, which can be both found in the java.lang.ref package. They provide the so-called soft and weak references, which are both considerably less powerful when it comes to prolonging the lifetime of an object.

Tags: , , ,
Author: Xion, posted under Programming » 1 comment

Dlaczego nie lubię typów referencyjnych

2009-12-16 11:54

Jeśli chodzi o C++, to nietrudno zauważyć, że często pozwalam sobie na sporo uwag krytycznych pod adresem tego języka. Oczywiście zawsze jest to krytyka konstruktywna :) Tym niemniej wiele jest tu rzeczy, o których można powiedzieć, że w innych językach zostały pomyślane lepiej (łącznie z takimi, których w C++ nie ma, a przydałyby się).
Dlatego dzisiaj będzie trochę nietypowo. Chcę bowiem wspomnieć o problemie, który w językach pokroju C# czy Javy potrafi doprowadzić do powstawania trudnych do wykrycia błędów – i który jednocześnie w C++ w zasadzie nie występuje wcale.

Mam tu na myśli semantykę referencji, czyli pewien szczególny sposób odwoływania się do szeroko rozumianych obiektów w kodzie. Klasy, a właściwie to prawie wszystkie typy poza podstawowymi (jak liczby czy znaki), są w C# i Javie obsługiwane w ten właśnie sposób; dlatego czasami nazywa się je typami referencyjnymi.
Najważniejszą cechą takich typów jest fakt, że należące do nich zmienne nie zawierają bezpośrednio instancji obiektów. Jeśli na przykład Foo jest klasą, to deklaracja:

  1. Foo x;

nie sprawi, że pod nazwą x będzie siedział obiekt typu Foo. x będzie tutaj zaledwie odwołaniem do takiego obiektu – w tym przypadku zresztą odwołaniem pustym, niepokazującym na nic.
Jest to zachowanie diametralnie różne od typów podstawowych, jak choćby int. Ale idźmy dalej – skoro mamy zmienną mogącą trzymać odwołanie (czyli referencję) do obiektu, to pokażmy nią na jakiś obiekt, na przykład taki zupełnie nowy:

  1. x = new Foo();

A że w prawdziwym programie zmiennych i obiektów jest zawsze mnóstwo, to wprowadźmy na scenę jeszcze parę:

  1. Foo y = x;
  2. y.SomeValue = 4; // hmm...

No i zonk, można powiedzieć… Nikt aczkolwiek tego nie powie, bo dla każdego programisty C#, Javy itp. istnienie wielu referencji do tego samego obiektu jest rzeczą całkowicie naturalną. Jednak wiem, że podobny kod dla dowolnego typu liczbowego (zastąpiwszy ostatnią linijkę przez y += 4; lub coś tym w guście) zachowałby się zupełnie inaczej. Wiem też, że kiedyś byłem zmuszony wykonać kilka empirycznych testów, by się o tym naocznie przekonać; było to jeszcze w Delphi, a powodem były oczywiście jakieś “dziwne” błędy, na które natrafiłem w jednym ze swoich programów. Źle użyte typy referencyjne łatwo mogą być bowiem przyczyną takich błędów, które zresztą bywają potem trudne do wykrycia.

Bez jakiegoś rodzaju referencji nie da się rzecz jasna wyobrazić sobie użytecznego języka programowania. Sęk w tym, że w C# czy Javie używanie ich nie jest opcją do stosowania w tych przypadkach, które tego wymagają – jest koniecznością wymuszoną przez sam fakt programowania z użyciem klas i obiektów. To całkiem inaczej niż w C++, gdzie w tym celu trzeba wyraźnie zaznaczyć swoje intencje (najczęściej poprzez użycie typów wskaźnikowych).
W tworzeniu oprogramowania istnieje tzw. zasada najmniejszego zdziwienia (principle of least astonishment). Mówi ona, że przy alternatywie równoważnych przypadków powinno się wybrać ten, który u użytkownika końcowego będzie powodował mniejsze zdziwienie. Czy typy referencyjne zachowujące się zupełnie inaczej niż typy podstawowe i “same” zmieniające swoją zawartość nie są przypadkiem złamaniem tej reguły?…

Tags: , , ,
Author: Xion, posted under Programming, Thoughts » 12 comments

C#: referencje i porównywanie

2008-11-17 14:54

W C# obiekty zwykle dostępne są poprzez referencje. Zatem zmienna typu T (jeśli jest on klasą) nie zawiera samego obiektu T, lecz tylko odwołanie do niego. Porównując dwie takie zmienne przy pomocy operatora == domyślnie sprawdzamy więc, czy pokazują one na ten sam obiekt. Podobnie jest też przy użyciu domyślnej wersji metody System.Object.Equals; robi ona dokładnie to samo, co wspomniany operator. Można mądrze powiedzieć, że oba mechanizmy sprawdzają relację identyczności obiektów.

Czasami jednak chodzi nam o coś innego: chcemy sprawdzić, czy dwa obiekty są równe, np. w sensie zawartości swoich pól. Taka równość może zachodzić także wtedy, gdy obiektyte zostały stworzone zupełnie niezależnie. Znów można mądrze stwierdzić, że chcąc dokonać takiego sprawdzenia, realizujemy dla obiektów semantykę wartości. Co wtedy zrobić?… Ano przeciążyć tudzież nadpisać wspomnianą metodę Equals i/lub operator ==.
I tu zaczynają się schody, bo wcale nie jest łatwo zrobić to poprawnie, a jeszcze trudniej jest zrobić to sensownie. Wszystko zależy od tego, czy nasza klasa ma realizować wyłącznie ową nieszczęsną semantykę wartości czy też czasami będziemy jednak sprawdzać, czy dwie referencje pokazują na ten sam obiekt (a nie na dwa równe obiekty).

  • Jeśli tak (tzn. tylko sprawdzanie równości obiektów), to powinniśmy przeciążyć zarówno operator ==, jak i metodę Equals – i to przeciążyć tak, aby działały tak samo. Wtedy zwykle zajmujemy się najpierw operatorem, a później wykorzystujemy go w metodzie:
    1. struct Foo
    2. {
    3.     private int field;
    4.  
    5.     public static bool operator == (Foo a, Foo b)    { a.field == b.field; }
    6.     public override bool Equals(Object obj)
    7.         { return obj is Foo && this == (Foo)obj; }
    8. }

    W takiej sytuacji prawdopodobnie powinniśmy też posłużyć się strukturą. Najpewniej chodzi nam bowiem o typ, który ma zachowywać się jak podstawowy: czyli coś w stylu wektora, macierzy, kwaternionu, itp.

  • W przeciwnym przypadku (czyli: czasem porównujemy wartości, a czasem referencje) klasa powinna pozostać klasą, lecz należy przedefiniować jej metodę Equals. To jej powinniśmy używać, do sprawdzenia relacji równości między obiektami. Kiedy zaś chcemy zwyczajnie porównać referencje, możemy wtedy uciec się do operatora ==.

Uff, całkiem to zawiłe, prawda? Niestety nie jest łatwo uchwycić różnicę między równością a identycznością – a w językach, które obiekty realizują przez referencję sytuacja komplikuje się jeszcze bardziej. Zaś już chyba całkiem rozmywa się wtedy, gdy niektóre typy traktujemy per wartość, a niektóre per referencja…
A tak przecież jest C#. I jednocześnie podobno to jeden z prostszych języków do nauki i użytkowania :D

Tags: , ,
Author: Xion, posted under Programming » 5 comments

Wskaźniki i referencje jako parametry

2008-07-09 19:07

Kiedy w C++ chcemy przekazać do funkcji odwołanie do obiektu (zezwalające na jego modyfikację wewnątrz funkcji), mamy do wyboru dwie metody. Ta alternatywa to posłużenie się wskaźnikiem albo referencją:

  1. void Function(Foo* pFoo);
  2. void Function(Foo& foo)

Czy istnieje uniwersalna odpowiedź na to, którą wybrać? Chyba nie. Jeśli chodzi o wskaźnik, to za jego użyciem może przemawiać:

  • Możliwość przekazania odwołania pustego, jeśli parametr nie jest obowiązkowy. Obejmuje to oczywiście zdefiniowanie NULL jako domyślnej wartości dla tego parametru w deklaracji funkcji. Nie jest to możliwe dla referencji (w C++).
  • Fakt, że w wywołaniu funkcji bardziej widoczne jest to, iż przekazany do niej za pośrednictwem wskaźnika obiekt może się zmienić. Jeśli na przykład obiekt ten jest zmienną lokalną, to konieczne jest posłużenie się operatorem &, który daje o tym jakąś widoczną wskazówkę (nie tak jasną jak ref/out w C#, ale zawsze). Nie jestem też wielkim fanem notacji węgierskiej, lecz w przypadku wskaźników stosowanie przedrostka p wydaje mi się akurat wskazane i w tym kontekście też zwiększa czytelność wywołania funkcji, wskazując, że przekazywany obiekt (alokowany na stercie) też może się zmienić.
    1. Foo foo; Foo* pFoo = new Foo();
    2. Function (&Foo); Function (pFoo); // funkcja może zmienić obiekt

Z kolei referencje mogą się popisać innymi zaletami:

  • Nie można do nich przekazać odwołania pustego. To może być zaletą, jeśli taka sytuacja jest niepożądana. Ponadto brak konieczności sprawdzania tego, czy referencja jest “pusta”, może lekko poprawić wydajność kodu generowanego przez kompilator.
  • Składnia użycia obiektu przekazywanego przez referencję zwykle bywa bardziej przejrzysta. Jest tak zwłaszcza wtedy, gdy używamy względem niego operatorów. Na przykład kolekcja dostępna przez wskaźnik musiałaby być indeksowana przez (*pArray)[i], zaś przez referencję po prostu jako array[i].

Widać więc, że jeśli kwestia odwołania pustego nie jest dla nas istotna, to decyzja może być trudna. Ale naturalnie jest tak tylko wtedy, gdy zechcemy się nad takimi sprawami zastanawiać ;]

Tags: ,
Author: Xion, posted under Programming » 6 comments

Tajemnice stałych referencji

2008-03-13 21:47

Zapewne wielu programistów C++ zetknęło się z taką lub podobną sytuacją. Oto w pocie czoła napisana funkcja w postaci podobnej do tej:

  1. void Foo(string s) { /* ... */ }

zostaje obejrzana przez bardziej doświadczonego kodera, który stwierdza: “Powinieneś użyć tutaj stałej referencji jako parametru – czyli napisać:

  1. void Foo(const string& s)

Unikniesz wtedy niepotrzebnego kopiowania stringa”. Wówczas możemy zrobić wielkie oczy i zdziwić się bardzo, zwłaszcza jeśli wiemy co nieco o referencjach w C++. Mimo to sugestia ta jest jak najbardziej na miejscu i powinniśmy się do niej stosować. Wstyd przyznać się, że przez dłuższy czas niesłusznie brałem ją tylko na wiarę, lecz na szczęście jakiś czas temu dowiedziałem się dokładnie, o co tutaj chodzi. Tą cenną wiedzą oczywiście się podzielę :)

Wiadomo, że w C++ referencje to w zasadzie to samo, co (raczej rzadko używane) stałe wskaźniki – czyli zmienne w typu T* const. Różnią się one aczkolwiek składnią: wszystkie dereferencje są dokonywane przezroczyście i kod wygląda tak, jakbyśmy posługiwali się zwykła zmienną docelowego typu, na który referencja wskazuje (czyli T). Podobnie stałe referencje są odpowiednikami stałych wskaźników na stałe (const T* const), czyli takich, które nie pozwalają ani na modyfikację obiektu wskazywanego, ani na zmianę samego wskazania.
W każdym przypadku wskaźniki muszą jednak na coś pokazywać; na coś, co ma określone miejsce w pamięci, czyli adres. W zasadzie podobna reguła dotyczy też referencji – z jednym małym, ale jakże ważnym wyjątkiem.

Otóż powyższą funkcję (w wersji ze stałą referencją jako parametrem) możemy bez problemów wywołać tak, jak poniżej:

  1. Foo ("Hello world");

Niby nic nadzwyczajnego, ale zauważmy, że tworzony jest tutaj tymczasowy obiekt string, na który następnie pokazuje referencja w ciele naszej funkcji. Błąd standardu lub kompilatora? Wręcz przeciwnie – it’s not a bug, it’s a feature :)
Po prostu w C++ stałe (i tylko stałe) referencje mogą poprawnie wskazywać na obiekty tymczasowe. Życie takich obiektów jest wówczas przedłużane aż do czasu wyjścia poza zasięg istnienia referencji. W naszym przypadku utworzony tymczasowy obiekt string będzie więc dostępny w całej funkcji.

Biorąc pod uwagę fakt, że zamiana deklaracji string s na const string& s nie kosztuje nas nic (wewnątrz funkcji do parametru odwołujemy się tak samo), możemy zerowym kosztem zyskać sporą optymalizację. Przekazanie referencji kosztuje przecież tyle samo co przekazanie wskaźnika i na pewno jest nieporównywalnie tańsze niż wykonywanie kopii całego napisu.
Dlatego też nie tylko w przypadku klasy string, ale i we wszystkich podobnych sytuacjach obiekty powinniśmy przekazywać właśnie przez stałe referencje.

Tags: , ,
Author: Xion, posted under Programming » 4 comments

Referencje do klas w Delphi

2007-08-20 11:30

W ramach kontynuacji przeglądu nietypowych konstrukcji językowych – który to nieopatrznie rozpocząłem, zajmując się pętlami w Pythonie – obejrzymy sobie jeden z elementów języka Object Pascal. Są to referencje do klas, zwane też czasem metaklasami.

Ten dziwny twór działa jak odwołanie wskazujące na klasę jako typ, a nie na jej konkretny obiekt. Deklaruje się go mniej więcej w taki sposób:
[delphi]type TClass = class of TObject;[/delphi]
Zmienne należące do tak zdefiniowanego typu TClass mogą pokazywać na wszystkie klasy dziedziczące po TObject. Innymi słowy, takie zmienne są swego rodzaju dynamicznymi aliasami na nazwy klas; używając ich, nie musimy nawet wiedzieć, z jakiego typu klasą konkretną mamy do czynienia. Przypomina to oczywiście normalny dynamiczny polimorfizm obiektów, osiągany przy pomocy funkcji wirtualnych. Tutaj jest to niejako dynamiczny polimorfizm samych klas.

Użycie takiego typu referencyjnego może wyglądać choćby tak:
[delphi]type TFoo = class(TObject) // klasa dziedzicząca po TObject
// …
end;

var
AnyClass : TClass; // zmienna będąca referencją do klasy
AnyObject : TObject; // zwykłe odwołanie do obiektu
begin
AnyClass := TFoo; // referencja pokazuje na klasę TFoo
AnyObject := AnyClass.Create; // tworzy obiekt klas TFoo przy pomocy referencji
end;[/delphi]
Ten przykład pokazuje, że w Delphi przy pomocy referencji do klas możliwe jest łatwe zrealizowanie wzorca wirtualnego konstruktora. Nie musimy bowiem wiedzieć, na jaką klasę wskazuje referencja, a utworzony obiekt możemy “odebrać” posługując się zmienną typu bazowego (tutaj TObject).

Co na to C++? Nie ma tam naturalnie podobnej konstrukcji. Zbliżone do niej – w sensie możliwości korzystania z jakiegoś typu bez wiedzy, czym on naprawdę jest – są parametry szablonów. Podstawowa różnica polega jednak na tym, że szablony są rozwijane w trakcie kompilacji i “wartości” tych parametrów są niezmienne.
Nie wiem, czy można w jakiś sposób zaimplementować w C++ metaklasy o funkcjonalności zbliżonej do powyższej. Znając możliwości C++, to całkiem prawdopodobne :) Ich ewentualny brak nie były jednak jakoś szczególnie dotkliwy, gdyż większość ich zastosowań z powodzeniem daje się zastąpić szablonami lub zwykłymi funkcjami wirtualnymi.

Tags: , ,
Author: Xion, posted under Programming » 4 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.