Monthly archive for September, 2007

Antywzorce

2007-09-21 20:01

Kto koduje trochę dłużej z użyciem technik programowania obiektowego, ten zapewne zna ideę wzorców projektowych. Są one czymś w rodzaju przepisów na określone klasy i związki między nimi, które rozwiązują konkretne problemy. Przykładowo znany wzorzec Iterator stwarza uniwersalny sposób na dostęp do elementów kolekcji – niezależnie od tego, czy jest to lista, drzewo, zwykła tablica, itp.

Ale oprócz tych pożytecznych wzorców istnieją też… antywzorce. Zdecydowanie nie są one instrukcją, jak pewne fragmenty programów pisać. O nie, wręcz przeciwnie – mówią one, jak ich nie pisać i pokazują typowe oraz mniej typowe błędy popełnione we wszystkich fazach tworzenia oprogramowania.
Więcej o nich można poczytać choćby w Wikipedii. Zamieszczona tam lista jest niezwykle obszerna, ale trudno się dziwić – mówimy przecież o sposobach, jak zrobić coś źle, a w tej dziedzinie ludzkość ma wybitne osiągnięcia :)

Do ciekawszych (i pouczających) przykładów takich antywzorców należą chociażby:

  • Magiczne liczby – obecność w kodzie wartości liczbowych (poza trywialnymi w rodzaju 0 czy 1) bez ich podpisania komentarzami lub pośrednictwa stałych
  • Boski obiekt – klasa tak wielka i złożona, że zawiera się w niej cała funkcjonalność programu. Ktoś, kto używa takiego potworka najpewniej nie rozumie zasad programowania obiektowego i ma złe strukturalne przyzwyczajenia
  • Magiczny przycisk – pisanie kodu wykonującego wewnętrzne czynności programu bezpośrednio w treści procedur zdarzeniowych interfejsu użytkownika
  • Uniwersalny wytrych – stosowanie podobnego lub tego samego rozwiązania dla zupełnie odrębnych problemów
  • Sekwencja pętla-switch – niezwykle “pomysłowy” sposób na zakodowanie kilku następujących po sobie czynności przy pomocy pętli i instrukcji wyboru, która zależnie od tego czy jest to 1., 2., czy N-ta iteracja wykonuje jakieś z góry ustalone czynności. Przyznam, że gdy to pierwszy raz zobaczyłem, nie mogłem wprost uwierzyć, że ktoś może wpaść na coś tak niedorzecznego

Jak widać ten antywzorce dotyczą wielu różnych aspektów programowania i projektowania, i czasami ich niepoprawność wcale nie jest taka oczywista. (Chociaż na pewno ostatnia pozycja z listy powyżej jest aż nazbyt oczywista). Warto zatem zapoznać się z nimi; dzięki temu możemy znaleźć inspirację zarówno do unikania błędów, jak i ich popełniania ;]

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

Zespół napięcia przedkompilacyjnego

2007-09-20 20:48

Wyobraźmy sobie, że kończymy właśnie dużą partię kodu – kilkadziesiąt albo kilkaset linijek. Zamykamy ostatnią klamerkę (ewentualnie piszemy end lub coś w tym guście), stawiamy ostatni średnik. Potem jeszcze tylko kilkukrotne przeciąganie suwaka paska przewijania, kilka pobieżnych rzutów oka na powstały listing i… kompilujemy.
Co się wtedy dzieje? Ano zwykle w tym momencie zatrzymuje nas jakiś trywialny błąd składniowy :) W sumie nic wielkiego, jakiś brak średnika albo powtórzony nawias; poprawiamy i próbujemy dalej. Po kilku razach w końcu zaakceptuje nasz program w całości – i zwykle dopiero wtedy zaczyna się prawdziwe debugowanie :]

Nie da się ukryć, ze czas tuż przed i w czasie kompilacji może być dość stresujący. Niby należałoby się już cieszyć, że oto wykonaliśmy jakiś (zwykle niewielki) kroczek na drodze do działającej całości. Zwłaszcza jeśli przez długi czas kod był rozgrzebany i nie dało się kompilować, domknięcie go z powrotem do kompilowalnej całości – uzupełnionej zapewne o nowe funkcje i możliwości – jest bardzo satysfakcjonujące.
Jednocześnie jednak to chwila próby. Pisanie kodu to w pewnym stopniu akt twórczy, ale jego kompilacja to proces najzupełniej prozaiczny, który sprawdza, jak nasze pomysły wypadają pod względem poprawności językowej. I jakby tego było mało, kod skompilowany na początku jest zwykle bardzo odległy od kodu poprawnego. Kompilacja jest więc zapowiedzią kolejnej, jeszcze “przyjemniejszej” fazy programowania – testów.

Jak zatem załagodzić kompilacyjny stres? Myślę, że należy wypracować sobie odpowiadającą nam częstotliwość przeprowadzanych kompilacji. Jeżeli bowiem będziemy przeprowadzali je zbyt rzadko, wówczas każda próba będzie trwała dłużej (więcej kodu = więcej błędów składniowych i tym podobnych), a w wynikowym kodzie ukrytych będzie więcej błędów do usunięcia podczas debugowania. Z drugiej strony trudno jednak kompilować i testować każdą drobnostkę, bo można całkowicie utonąć w szczegółach.

Czyli mówiąc wprost: ciężkie jest życie programisty :) W miarę nabywania doświadczenia okazuje się aczkolwiek, że potrafimy bez większych problemów doprowadzić do kompilacji coraz dłuższe partie kodu, w których w rezultacie nie ukrywa się już tak wiele błędów jak wcześniej. W programowaniu ważne jest bowiem, aby nie zrażać się pierwszymi (ani następnymi) niepowodzeniami.

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

Eksperyment vim

2007-09-19 15:01

Ci, którzy oprócz (albo zamiast) popularnych Okienek pracują w innych systemach operacyjnych, zapewne znają pewien edytor tekstu o nazwie vim. To jeden z tych kultowych programów ery początków Uniksa, które nadal mają swoich zagorzałych zwolenników. Wśród dzisiejszych programistów nie jest on może bardzo popularny, ale przez to jego używanie czasami wydaje się być jakimś wyższym stopniem wtajemniczenia. Sam aczkolwiek znam kilku koderów, którzy się nim posługują. Parę tygodni temu postanowiłem więc sprawdzić, co w trawie piszczy i zobaczyć, czy w praktyce legendarny interfejs vima faktycznie sprawuje się tak dobrze.

Dla mniej zorientowanych wyjaśniam, że vim (podobnie jak jego poprzednik vi) to edytor tekstu oryginalnie przeznaczony do pracy w konsoli. Jego interfejs jest tak naprawdę wybitnie ascetyczny, bo oprócz treści dokumentu składa się na niego tylko jednolinijkowy wiersz statusu i poleceń, umieszczony na dole.
Cechą charakterystyczną tego edytora jest między innymi to, że wszystkie polecenia można w nim wykonać przy pomocy alfanumerycznej części klawiatury. Nawet poruszanie się po tekście jest możliwe nie tylko strzałkami, ale też klawiszami H, J, K i L! Tak działają one aczkolwiek tylko w tzw. trybie Normalnym. vim ma oprócz tego też tryb Wstawiania (bardzo podobny do zwykłych edytorów tekstu) oraz Wizualny (do zaznaczania). Przełączanie się między nimi odbywa się oczywiście za pomocą odpowiednich skrótów klawiaturowych.

gVim w trybie normalnym gVim w trybie wstawiania gVim w trybie wizualnym

Dla nowicjusza wyglądają one cokolwiek dziwnie, podobnie jak sam sposób obsługi edytora polegający właśnie na przełączaniu między trybami. Jak mówi anegdota, najprostszy sposób na wygenerowanie losowego ciągu znaków to: posadzić początkującego przed vimem i kazać mu wyjść :)
Na początek warto więc wiedzieć, że służy do tego komenda :q (lub :q!) :P Nauka reszty podstaw potrzebnych do obsługi edytora nie zajmuje zresztą wiele czasu, jeżeli zrobimy to z tutorialem dołączonym do windowsowej wersji edytora – gVim.

Jakie można z tego odnieść korzyści? Zaprzysięgli fani vima utrzymują, że używanie go znacząco zwiększa szybkość i wygodę pisania, także kodu. Rzeczywiście posiada on kilka funkcji użytecznych dla programistów – jak na przykład wyszukiwanie par nawiasów. Poza tym koderzy cały czas stukają w klawiaturę, zatem możliwość obsługi edytora kilkunastoma klawiszami umieszczonymi blisko siebie powinna teoretycznie usprawniać pracę.
W praktyce obserwowałem kilka razy programistów pracujących w vimie i nie mogłem oprzeć się wrażeniu, iż strasznie się oni męczą. Sam próbując używać tego programu do codziennej pracy zauważyłem, że zmusza on do myślenia o strukturze tekstu – słowach, linijkach – o wiele bardziej niż ‘zwyczajne’ edytory. Ostatecznie to, że vim rzeczywiście poprawia wygodę programowania nie wydało mi się wcale oczywiste.
Jest natomiast wcale niewykluczone, że ten edytor jest bardzo dobry do pisania zwyczajnych tekstów dla osób, które biegle opanowały sztukę stukania w klawisze wszystkimi palcami obu dłoni. Wówczas fakt, że nie muszą one zdejmować dłoni z klawiatury aby np. coś poprawić, jest na pewno korzystny. Kod jest natomiast znacznie mniej regularnym tekstem, a szybkość jego wpisywania ma drugorzędne znaczenie. Większość czasu poświęconego na programowanie zajmuje przecież myślenie o kodzie, a nie jego wstukiwanie.

Stąd wniosek, że znajomość vima powinna być sporym atutem każdej sekretarki. Czemu wobec tego wszyscy do pisania zwykłych tekstów używają tylko (Open)Office? ;]]

Tags: ,
Author: Xion, posted under Applications » 13 comments

Unie i konstruktory

2007-09-18 19:17

Przychodzi czasem ochota, aby zapewnić dostęp do tych samych danych na różne sposoby. Ten pomysł można zrealizować w sposób poprawny albo i nie :) Dzisiaj właśnie przekonałem się ostatecznie, dlaczego stosowana przeze mnie metoda należała do tej drugiej kategorii.

Otóż dość powszechnie wykorzystuję pewną strukturę o nazwie SMouseEventArgs, która docelowego zawiera informacje o jakimś zdarzeniu związanym z myszą (klik, przesunięcie, itd.). Wśród tych informacji jest między innymi pozycja kursora w momencie zajścia zdarzenia, która początkowo była opisana po prostu przez dwa pola X i Y.

W pewnym momencie zauważyłem jednak, że dość często stosuję konstrukcję podobną do tej:

  1. SMouseEventArgs mea(/*...*/);
  2. // ...
  3. SomeMethod (POINT2F(mea.X, mea.Y));

gdzie POINT2F jest strukturą opisującą punkt na płaszczyźnie. Aby zaoszczędzić sobie pisania (i konstrukcji tymczasowego obiektu) postanowiłem w przypływie kreatywności dodać do SMouseEventArgs drugi sposób dostępu do tych współrzędnych – pole Position. Jak? Otóż… przy pomocy unii:

  1. struct SMouseEventArgs
  2. {
  3.    union
  4.    {
  5.       struct { float X, Y; };
  6.       POINT2F Position;
  7.    };
  8.  
  9.    // ...
  10. };

Sęk w tym, że POINT2F jest porządną strukturą i zawiera między innymi kilka konstruktorów. A jest niedozwolone, by obiekt klasy posiadającej zdefiniowany przez programistę konstruktor albo nietrywialny destruktor mógł być elementem unii.
Kompilator raczył mnie więc błędem, ale wówczas w kolejnym przypływie kreatywności stosowałem “objeście” w postaci anonimowej struktury:

  1. struct { POINT2F Position; };

I byłem święcie przekonany, że przecież skoro POINT2F nie alokuje własnej pamięci, nie otwiera plików ani nie robi żadnych innych tego typu czynności, po których trzeba by sprzątać, to przecież nic złego nie może się stać…

Naturalnie byłem w błędzie :) Dalszym elementem układanki jest konstruktor SMouseEventArgs, przyjmujący kilka parametrów i inicjalizujący nimi strukturę:

  1. SMouseEventArgs(float x, float, y /*... */) : X(x), Y(y) { }

Na oko niby wszystko jest w porządku. Tylko dlaczego cokolwiek byśmy przekazali jako x i y, w wynikowej strukturze zawsze i tak współrzędne będą zerami?!
Ot, kolejna nierozwiązania zagadka Wszechświata. Przynajmniej do chwili, gdy uświadomimy sobie dwie rzeczy:

  • konstruktor zawsze inicjalizuje wszystkie pola klasy – nie tylko te na liście inicjalizacyjnej
  • kolejność inicjalizacji tych pól jest taka sama jak kolejność ich deklaracji w klasie

Aplikując te zasady do powyższego przypadku, mamy bardzo ciekawy scenariusz. Mianowicie pola X i Y są poprawnie wypełniane parametrami konstruktora SMouseEventArgs, lecz w chwilę potem to samo miejsce pamięci jest… nadpisywane przez konstruktor POINT2F. Dlaczego? Ano dlatego, że pole Position też musi zostać zainicjowane, a domyślny konstruktor POINT2F wstępnie ustawia je na punkt (0,0).

Mniej więcej takie są skutki prób bycia mądrzejszym od twórców języka i kompilatora :) Można by oczywiście brnąć dalej w to rozwiązanie, zmieniając kolejność deklaracji pól albo jawnie inicjalizować pole Position zamiast X i Y. Cały czas jednak jest to stąpanie po cienkim lodzie.
Dlatego chyba najwyższy czas ograniczyć swoją kreatywność i następnym razem zastosować może mało efektowne, ale za to stuprocentowo bezpieczne metody dostępowe :)

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

Nałóg wynajdywania nałogów

2007-09-17 23:03

Czasami czytając gazety i serwisy internetowe (ewentualnie śledząc to, co podaje telewizja), wydaje się, że jakiś temat przewija się nadspodziewanie często. Jak to ładnie ktoś powiedział, w końcu można zacząć bać się otwierać konserwę, aby na niego nie natrafić :) Z początku jednak można nie zdawać sobie z tego sprawy i dopiero któryś z rzędu artykuł uświadamia nam: “Ej, przecież już czytałem o czymś podobnym!”.

Screen z World of WarcraftW tym przypadku moją uwagę zwrócił ten tekst publikowany na Onecie. Opowiada on o grach sieciowych, a konkretniej o MMORPGach, a jeszcze konkretniej o grze World of Warcraft i grającej w nią pewnej dziewczynie. Muszę przy tym zaznaczyć, że nie jest to kolejny tekst z tezą streszczającą się w słowach: “jakie te gry są brutalne, bezwartościowe i w ogóle złe”, tylko całkiem porządne przedstawienie tematu. A tematem jest oczywiście uzależnienie od wspomnianych gier.
Przypomniało mi się wówczas, że w zeszłotygodniowym Newsweeku (37/2007) widziałem reportaż opisujący nałóg… tworzenia i utrzymywania kolejnych stron w Wikipedii. Okazuje się, że mechanizm funkcjonowania tej encyklopedii, w której każdy może edytować treść, ma swoją drugą stronę. Ktoś może bowiem tę treść po prostu zepsuć i aby temu przeciwdziałać, autor danego hasła z konieczności staje się jego strażnikiem. A jeżeli doda kolejne i kolejne strony, nagle może się okazać, że ich pilnowanie zajmuje mu większość wolnego (i nie tylko wolnego) czasu.
Na deser mogę jeszcze przytoczyć dzisiejszy nius z cyklu ‘Ktoś z Dalekiego Wschodu umarł po iluśtam godzinach grania’. Tym razem był to Chińczyk i jego “wynik” to trzy dni, a wszystko jak zwykle rozegrało się w anonimowej kafejce internetowej, gdzie nikt na nikogo nie zwraca uwagi.

Słowo-klucz łączące te wszystkie treści to oczywiście nałóg lub ewentualnie uzależnienie. Ich rodzajów przybywa ostatnio w niewiarygodnym tempie. Można powiedzieć, że “zwykłe” papierosy, alkohol czy narkotyki to już oklepane banały, a teraz “w modzie” jest co najmniej uzależnienie od wspomnianych gier, komputera czy Internetu albo pracy, zakupów, clubbingu, szybkiej jazdy samochodem, plotkowania czy jedzenia.
Dziwna to lista, prawda? Aż chciałoby się do niej dodać – powiedzmy – telefony, komunikację miejską, gazety… A zresztą, nie powinniśmy się ograniczać: należy jak najszybciej rozpoznać kliniczne objawy uzależnienia od elektryczności, wodociągów i centralnego ogrzewania :P Jak widać w miarę rozwoju medycyny faktycznie zaczyna się okazywać, że nikt tak naprawdę nie jest już zdrowy :)

Żeby więc nie pozostać w tyle, pragnę niniejszy oświadczyć, że sam jestem uzależniony od:

  • regularnego publikowania nowych notek na blogu (od tego przede wszystkim!)
  • codziennego czytania nowych cytatów z IRCa na bash.org.pl
  • przeglądania nowych wątków na forum Warsztatu i odpisywania na nie
  • uporczywego i niekończącego się dodawania nowych możliwości do swojej silniko-biblioteki C++
  • wymyślania i spisywania pomysłów na nowe programy, które potencjalnie mógłbym kiedyś zrealizować
  • realizowania mniej więcej 1% tych pomysłów :)
  • bezproduktywnego marnowania czasu na warsztatowym kanale IRC w tak zwanym międzyczasie

Lista ta dotyczy aczkolwiek tylko jednej dziedziny życia i z pewnością nie jest kompletna. Już po niej widać jednak, że mojemu zdrowiu zagraża całe mnóstwo paskudnych nałogów, z którymi powinienem czym prędzej zacząć walczyć.

Ale jak?… Chyba dla odmiany trzeba by zająć się z powrotem graniem w WoW ;D


Author: Xion, posted under Games, Life, Thoughts » 1 comment

System GUI #7 – Pole tekstowe

2007-09-16 21:16

Wśród standardowych kontrolek spotykanych w każdym systemie graficznego interfejsu jest jedna, która zdecydowanie wyróżnia się stopniem skomplikowania. Z wierzchu to tylko mały, podłużny prostokąt z wpisanym ciągiem znaków, który można modyfikować. Jednak mechanizmy sprawiające, że jest to możliwe, nie są wcale takie proste.
Mówię tu oczywiście polu tekstowym, znanym też jako textbox, editbox lub po prostu edit.

Powodów, dla których właśnie ta kontrolka wyróżnia się złożonością, jest przynajmniej kilka. Są to chociażby:

  • Kontrolowanie, jaki fragment tekstu jest w aktualnie widoczny w ramach kontrolki. Jak wiemy, pole tekstowe powinno oferować możliwość wpisywania tekstu dłuższego (pikselowo) niż jego rozmiary, dlatego często część jego zawartości będzie niewidoczna. Należy jednak zapewnić możliwość przewijania (klawiszami strzałek wraz z Ctrl oraz Home i End), aby możliwe było ukazanie innego fragmentu tekstu.
  • Przycinanie w polu tekstowym

  • Obsługa kursora tekstowego, czyli tzw. karetki. Oprócz przesuwania za pomocą klawiatury użytkownik bardzo słusznie będzie oczekiwał, że jest to możliwe także poprzez kliknięcie myszą. Wymaga to więc istnienia sposobu na określenie, w jaki znak użytkownik trafił. A poza tym karetka musi jeszcze mrugać odpowiednio zachęcająco – w końcu dawniej nazywała się ona znakiem zachęty :)
  • Pole tekstowe z kursorem (karetką)

  • Możliwość zaznaczania tekstu. Tradycyjnie jest to możliwe poprzez przeciąganie, jak również z klawiatury (z użyciem klawisza Shift). Kontrola stanu zaznaczenia to generalnie niezbyt złożona, ale też nie tak trywialna maszyna stanów
  • Zaznaczenie w polu tekstowym

Tak naprawdę choćby w systemie Windows pola tekstowe umożliwiają nieco więcej, jak na przykład możliwość cofnięcia ostatniej operacji, obsługa Schowka czy nawet menu kontekstowe. Takie cuda nie są chyba jednak potrzebne do szczęścia :)
Zauważmy też, że kontrolka umożliwiająca edycję dłuższego tekstu dzielonego wiersza to coś zupełnie innego niż zwykły textbox i jej możliwości są nieporównywalnie większe. Dochodzą tam bowiem paski przewijania, zaznaczanie rozciągnięte na kilka wierszy, i tak dalej.

Implementując proste pole tekstowe zauważyłem, że bardzo pomaga przy tym odpowiednia konstrukcja pewnych podstawowych mechanizmów GUI – jak mouse capture czy fokus klawiatury, modelu propagacji zdarzeń oraz modułu odpowiedzialnego za wypisywanie tekstu. Łącznie jednak wychodzi z tego i tak nadspodziewanie duża ilość kodu ze sporą liczbą zagmatwanych ifów :]

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

Przeciążanie destruktorów

2007-09-15 11:41

Trochę poprzednio ponarzekałem na technikę RAII stosowaną w C++, a raczej na towarzyszący jej brak wygodnej instrukcji finally. Było to być może odrobinę niesprawiedliwe, gdyż mechanizm ma dość duże możliwości – przynajmniej potencjalnie :)

Natrafiłem jakiś czas temu na Usenecie na ciekawy pomysł związany z tą właśnie techniką. Chodzi tu o wykrywanie, z jakiego powodu obiekt lokalny ma zostać zniszczony. Może się to bowiem odbyć w normalny sposób (gdy kończy się wykonanie odpowiedniego bloku kodu i program przechodzi dalej) lub w wyniku odwijania stosu podczas obsługi wyjątku. W obu przypadkach w C++ jest jednak wywoływany jeden i ten sam destruktor.
Jest to w porządku, jeżeli jego zadaniem jest tylko zwolnienie zasobu (czyli np. zamknięcie otwartego deskryptora pliku). Możemy sobie aczkolwiek wyobrazić zastosowanie RAII do tzw. transakcji:

  1. try
  2. {
  3.    CTransaction ts;
  4.    ts.DoSomething();  // jakaś operacja
  5.  
  6.    // ...
  7.    if (SomethingBadHappened())
  8.       throw std::exception("Failure!");
  9. }
  10. catch (...) { /* ... */ }

Transakcja to termin znany głównie programistom zajmującym się bazami danych lub innymi pokrewnymi dziedzinami “sortowania ogórków” ;) W skrócie, jest to taki ciąg operacji, który musi być zaaplikowany albo w całości, albo wcale. Jeżeli po drodze zdarzy się coś nieoczekiwanego i transakcję trzeba przerwać, wszystkie wykonane do tej chwili operacji powinny zostać odwrócone (rollback).
Można by to zrobić automatycznie w reakcji na rzucenie wyjątku, gdyby C++ pozwalał na wykrycie wspomnianych dwóch sposobów niszczenia obiektu. Ciekawym pomysłem na to jest dopuszczenie więcej niż jednego destruktora:

  1. class CTransaction
  2. {
  3.    public:
  4.       CTransaction() { Begin(); }
  5.       ~CTransaction() { Commit(); }
  6.       ~CTransaction(const std::exception&) { Rollback(); }
  7. };

Zwykły, bezparametrowy, byłby wywoływany w przypadku zwyczajnego opuszczenia bloku kodu. Natomiast destruktor przyjmujący parametr włączałby się wówczas, gdy niszczenie obiektu zdarzy się z powodu wyjątku. Taki destruktor “łapałby” więc na chwilę taki wyjątek – lecz nie po to, by go obsłużyć, ale wyłącznie w celu odpowiedniego zakończenia życia obiektu w sytuacji kryzysowej. Parametr takiego destruktora odpowiadałby typowi wyjątku, który ten destruktor miałby “łapać”.

Obecnie nie jest naturalnie możliwe stosowanie w C++ takiej konstrukcji. Istnieje jednak sposób na sprawdzenie, czy jesteśmy właśnie w trakcie obsługi jakiegoś wyjątku. Służy do tego mało znana funkcja uncaught_exception z przestrzeni std (nagłówek exception):

  1. CTransaction::~CTransaction()
  2. {
  3.    if (std::uncaught_exception())
  4.       // destruktor wywołany przez wyjątek - odwracamy transakcję
  5.       Rollback();
  6.    else
  7.       // normalne wywołanie destruktora - zatwierdzamy
  8.       Commit();
  9. }

Wprawdzie nie zapewnia ona dostępu do samego obiektu wyjątku (ani poznania jego typu), ale pozwala na zorientowanie się, czy taki wyjątek w ogóle wystąpił. A to, jak widać, najczęściej wystarczy. Tak więc chociaż przeciążanie destruktorów na pierwszy rzut oka brzmi interesująco (i intrygująco), nie jest, jak sądzę, zbytnio potrzebne.

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


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