Article View: pl.comp.objects
Article #4626Re: Referencje (Re: Prawda o C++)
From: Marcin 'Qrczak' Kowalczyk
Date: Fri, 22 Jan 1999 16:32
Date: Fri, 22 Jan 1999 16:32
238 lines
15075 bytes
15075 bytes
On Thu, 21 Jan 1999, Michal Mosiewicz wrote:
> Oczywiście kopiowanie referencji ma miejsce tylko wtedy, gdy po obu
> stronach operatora mamy typy obiektowe. Ze względu na to, że nie byłoby
> efektywne posługiwanie się w ten sposób typami prymitywnymi, tj. byte,
> int, long, float itp, to w przypadku zastosowania operatora przypisania
> mamy do czynienia z kopiowaniem przez wartość.
Dlaczego Java zakłada, że dla żadnej klasy to już nie byłoby efektywne?
Właśnie to dla mnie ją dyskwalifikuje.
> Jednakże te drobne niewygody redukuje fakt, że progamista dostaje
> narzędzie, które po prostu z góry narzuca mu wielokrotne
> wykorzystywanie tych samych obiektów. W przeciętnym projekcie C++ jest
> znacznie więcej niepotrzebnego kopiowania całych obiektów, a nie
> referencji.
Zabieranie jakiejś możliwości jest niewątpliwą zaletą tylko wtedy, kiedy
ta możliwość nigdy nie jest warta użycia. W tym miejscu tak nie jest.
> W wielu wypadkach robi się kopie tylko dlatego, że później nie będzie
> wiadomo, kto ten obiekt ma tak naprawdę skasować.
A to inny aspekt tej sprawy. Pewna forma gc czasem się przydaje (ale
u mnie na pewno nie będzie on totalny dla wyszystkich wskaźników - raczej
bliżej auto_ptr z C++owego STL).
> > W każdym razie wygląda na to, że w Javie nie ma referencji, przynajmniej
> > dla typów definiowanych przez użytkownika. Są same wskaźniki! Tyle że tego
> > na oko nie widać, bo każda funkcja oczekuje tych wskaźników i wskazywane
> > obiekty same się zwalniają.
>
> Tego zdania też nie rozumiem. Jeśli jakiś obiekt jest czymś
> identyfikowany i tenże identyfikator jest utożsamiany z obiektem, bez
> pośrednictwa dodatkowych operatorów, to dla mnie to jest referencja.
To jest wskaźnik, bo przypisanie (jak z porównaniem?) kopiuje adres, a nie
zawartość. Tego na oko nie widać, bo operator . w Javie odpowiada -> w C++,
a wszystkie funkcje oczekują takiego samego wskaźnika, jakim są wszystkie
zmienne obiektowe. Nigdzie nie ma jawnej dereferencji, bo wszędzie są
używane te wskaźniki.
> > Załóżmy, że chcę zdefiniować operację przypisz, która działa jak operator
> > przypisania, tzn.
> > a.przypisz (b);
> > jest dokładnie równoważne
> > a = b;
> > albo drugą wersję, która jest równoważna
> > b = a;
> > W szczególności jeśli dla pierwszej wersji wcześniej było c = a (a dla
> > drugiej c = b), to teraz wartość c ma się nie zmienić (mam nadzieję, że
> > dobrze rozumiem znaczenie = w Javie, której nie znam). Jak to zrobić?
>
> Hmm, to jest właśnie doskonały przykład tego jak C++ może wykoślawić
> obiektowe postrzeganie programowania.
Wręcz przeciwnie. To jest przykład na to, jak obiektowe postrzeganie
programowania nakłada sztuczne ograniczenia.
> Rozumiem, że jest tak: Mamy dwa różne obiekty, i wywołanie metody
> jednego z tych obiektów ma spowodować, że tenże obiekt stanie się
> drugim obiektem.
To jest przykład na to, że wywołanie metody obiektu oraz przypisanie nie
są jedynymi rodzajami czynności, jakie możemy wykonywać. Jeszcze jest
np. wywołanie funkcji, która w środku użyje przypisania do tego, co
przekażemy jej jako parametr. W ogólności wszystko to są przejawy wywołań
funkcji i nie widzę żadnego powodu, dla którego przypisanie miałoby być
jakoś wyróżnione.
Niech to więc nie będzie wywołanie metody jednego z tych obiektów, tylko
coś innego, tak żeby można było jakoś przekazać temu referencje do
wskaźników, którymi są a i b (wystarczy referencja na lewy - prawy może
być przekazany normalnie). Niech to będzie jakakolwiek składnia (ale bez
jawnego użycia =, bo np. będę chciał to potem rozszerzyć i nie przypiszę
zawsze, tylko przy spełnieniu jakiegoś warunku), w którą mogę wstawić
gdzieś a i b.
Bardziej praktycznym przykładem swap(a,b), które jest mniej więcej
równoważne:
temp = a;
a = b;
b = temp;
gdzie temp jest zmienną lokalną odpowiedniego typu. Nie da się? A więc
dziękuję bardzo za taki język. Nawet w Pascalu da się to zrobić (prawda,
że dla każdego konkretnego typu z osobna; czy w Javie są jakieś szablony,
czy też są tylko funkcje wirtualne i każdy typ jawnie musi określić, że
będzie mógł być używany jako wartość porównywalna?).
> Jak chcesz wykorzystać identyfikator a do tego, żeby wskazywał na
> obiekt identyfikowany zmienną b, to korzystasz z operatora
> przypisania.
Jeśli jakiejś operacji dostępnej w języku nie można zaemulować pod własną
nazwą, to język jest dla mnie za bardzo ograniczony. W szczególności C++
nie jest tutaj dobry: nie da się zdefiniować własnego ifa ani whila. Jak
widzę, Java jest jeszcze gorsza, przez brak referencji, które są konieczne
do ładnego zaimplementowania opertora przypisania. Brzydsza jego postać
jest możliwa, jeśli mamy prawdziwe wskaźniki (tak jest np. w C), ale
w Javie wskaźniki są szczątkowe: nie można tworzyć wskaźników do prostych
typów ani wskaźników do wskaźników do obiektów, czyli wskaźników do tego,
co jest pamiętane w zmiennych; z drugiej strony, obiekty są dostępne
wyłącznie przez wskaźniki.
> Co z tego, że jest ładnie, skoro natychmiast okazuje się, że tak
> pokręcony operator zerwał z logiką przynależności metod do obiektów i
> trzeba wymyśleć mechanizm zaprzyjaźniania, żeby go z powrotem
> "przykleić" do tych obiektów, z których został wyrwany.
Wręcz przeciwnie. To jest przykład na to, że logika przynależności metod
do obiektów jest ograniczona i nie odpowiada dobrze rzeczywistości.
Ja rozumiem, że inni mogą lubić co innego i kochają ograniczenia
programowania obiektowego. Nie będę im wchodził w drogę ani próbował siłą
nawracać; przepraszam za ten kawałek świętej wojny na pl.comp.objects
(jednak potrzeba nam pl.comp.lang.advocacy!). To jest tylko moje skromne
zdanie i efekt długich przemyśleń.
Jeszcze nie wiem, czy to się da w spójny sposób dokończyć, ani tym
bardziej, czy to nie będzie przeszkadzać dobrym stylom programowania.
Pojęcie dobrego stylu jest dość subiektywne. Na pewno nie będzie to
wymuszać stylu, którym niektórzy się zachwycają jako czysto obiektowym,
a dla mnie jest to zestaw sztucznych ograniczeń i naginania rzeczywistości
do sposobu myślenia według ograniczonych i dziwnych schematów.
Dla mnie operacja niekoniecznie jest wykonywana na jednym obiekcie tudzież
przez obiekt. Na przykład przy dzieleniu liczb nie powiedziałbym, że to
jedna liczba dzieli się przez drugą albo druga dzieli pierwszą przez
siebie. Wykonujemy operację dzielenia otrzymując nową liczbę na podstawie
dwóch innych i już. Jeśli wypisujemy liczbę na strumień, to rodzaj
strumienia i rodzaj liczby wspólnie wyznaczają szczegóły tej operacji.
Problem, czy ma to być operacja na strumieniu (wypisz to na sobie), czy
na liczbie (wypisz się na tym), jest dla mnie sztuczny i nie powinien
w ogóle się pojawiać.
Możliwość uzyskania liczby losowej z zakresu liczb całkowytych nie jest
jakąś szczególną cechą liczb całkowitych i w czasie projektowania liczb
całkowitych nie musimy myśleć o tym, że ktoś kiedyś będzie chciał
produkować liczby losowe z przedziałów przez nie wyznaczonych. Podobnie
z iteracją instrukcji po takim przedziale - a AFAIR w Smalltalku jest to
metoda liczb całkowitych... Z kolei uzyskanie losowego bitu nie wymaga
żadnego obiektu, na którym mielibyśmy działać.
Nie potrzebuję specjalnego pojęcia konstruktora z argumentami. To się
w praktyce nie różni od inicjalizacji zmiennej wynikiem normalnej funkcji!
Konstruktor ma sens tylko domyślny i kopiujący; plus destruktor. Modulo
pewne dodatkowe szczegóły dla umożliwienia większej efektywności.
Oprócz wymuszenia sztucznego dla mnie stylu myślenia o zwyczajnych
funkcjach obiektowość daje też pewne przydatne rzeczy: dziedziczenie
i polimorfizm, w tym klasy abstrakcyjne. I to się najprawdopodobniej
(tzn. według moich nieurzeczywistnionych jeszcze przemyśleń) da zrobić
bez konieczności zmuszania funkcji do wyróżniania któregoś ze swoich
argumentów! Na dodatek pod pewnymi względami lepiej niż np. w C++. Nie
widzę powodu (poza lenistwem kompilatorów), dla którego klasy miałyby
jawnie określać, jakie interfejsy implementują (tzn. w C++owej nowomowie:
z jakich abstrakcyjnych klas dziedziczą). W razie potrzeby (zapewne bardzo
rzadko) taką rzecz zawsze można zaemulować, wymieniając w interfejsie
martwą funkcję o określonej nazwie.
Najprawdopodobniej daje się też zrobić, żeby funkcja wywołana z dwoma
argumentami będącymi interfejsami (klasami abstrakcyjnymi) była wirtualna
ze względu na oba, tzn. żeby była wybierana wersja odpowiadająca danej
parze typów.
Dziedziczenie danych nie różni się w istocie od zawierania tych danych
jako pole (oczywiście poza Javą i podobnie pomylonymi w tej sprawie
językami, w której nie można po prostu włączyć obiektu danego typu jako
pole, tylko można najwyżej wstawić wskaźnik do niego albo dziedziczyć).
Żeby takie pole działało jak statyczne dziedziczenie, wystarczy, żeby
typ całości był konwertowalny na typ tego pola, dając jego wartość.
Dziedziczenie interfejsów formalnie nie istnieje: po prostu jeśli jakiś
interfejs zawiera wszystkie funkcje drugiego i być może jeszcze trochę,
to znaczy, że oczekując tego drugiego, można dostać ten pierwszy, skoro
wszystkie potrzebne instrukcje są dla niego zdefiniowane. Wystarczy więc
ułatwienie w składni definiowania interfejsu, które oznacza włączenie
zawartości jakiegoś innego interfejsu.
Interfejs powinien być wieloargumentowy, żeby można było wyrazić "wartość
typu T1 indeksowana wartościami typu T2 daje wartości typu T3" (dla
kochających nowomowę: wartości to obiekty, a typy to klasy). Dzięki temu
interfejsy przydadzą się też do odpowiedników szablonów funkcji z C++.
C++owa zasada "konkretny typ albo dowolny typ argumentu" jest mało
elastyczna, chociaż w większości sytuacji wystarcza. Zamiast obchodzić
ją sztuczkami z STLowymi traitsami (typedefy w specjalnych szablonach
klas parametryzowanych badanymi typami i funkcje przyjmujące dodatkowy
argument, którego typ służy do wyboru odpowiedniej wersji, a wartość jest
nieważna), wystarczy wymusić jawne określanie, jakich operacji wolno
używać w szablonie funkcji, a typy argumentów nie będą "dowolnymi typami,
dla których treść funkcji wychodzi sensowna", tylko typami pasującymi do
podanych interfejsów. W efekcie wszystko, co dotychczas, będzie można też
wyrazić (z koniecznością sformalizowania własności, jakie muszą spełniać
typy parametryzujące szablony funkcji - co w STLu jest tak czy siak,
tyle że nieformalne), a przy okazji otrzymamy możliwość dodatkowej
elastyczności (definiowanie różnych wersji procedury sortującej dla
abstrakcyjnych ciągów mających różne własności).
Przy okazji w ten sposób okazuje się, że pewne dwa podejścia są przejawami
wspólnego, ogólniejszego mechanizmu. Np. funkcje sortujące ciąg można
definiować dwoma językami: albo jako szablon funkcji (zdefiniowany dla
dowolnych typów, które sprawiają wrażenie ciągu porównywalnych wartości),
albo jako metodę abstrakcyjnej klasy ciągu abstrakcyjnych wartości
porównywalnych. U mnie oba podejścia sprowadzają się do tego samego: dla
dowolnych typów T1 i T2, gdzie T1 jest ciągiem T2, a T2 jest porównywalne
między sobą, definiuję następującą funkcję sortującą. Zapis, że argument
funkcji jest typu "abstrakcyjna wartość porównywalna", jest skrótem na
"dla dowolnego typu T, który jest porównywalny, argument może być typu T".
To, czy kompilator wygeneruje osobne wersje dla każdego użytego gdzieś
zestawu typów, czy też jedną wersję wywołującą funkcje wirtualne, robi się
niezależną sprawą optymalizacji; podobnie jak inline. W C++ kompilator
w zasadzie nie ma szans skompilować szablonu w jednym egzemplarzu; ja
daję mu taką szansę (która staje się jednak koniecznością, kiedy typ
robi się znany dopiero w czasie wykonania, tzn. kiedy przekazuję zmienną
abstrakcyjnego typu, a nie zmienną jakiegoś konkretnego typu pasującego
do interfejsu).
Prywatność nazw będzie rozwiązana jakoś inaczej. Na pewno nie na zasadzie
dostępności akurat z metod klasy (bo nie ma osobnego pojęcia metody);
prędzej będą to arbitralne "moduły" zawierające funkcje widzące siebie
nawzajem, ale na zewnątrz widoczne będą tylko wybrane. To jeszcze
muszę dokładnie wymyślić. Przy okazji znika pojęcie metod i atrybutów
statycznych - dla mnie to kolejny dziwoląg, który próbuje na siłę
dopasować potrzebny efekt do zasady przyporządkowywania wszystkiego
jakiemuś typowi.
Prawdopodobnie jeśli ktoś będzie chciał, to będzie mógł do pewnego stopnia
używać tego stylu. Naturalną konwencją będzie wsadzanie argumentu będącego
odpowiednikiem obiektu, którego metodę definiujemy, na lewo od nazwy
funkcji. Ale to nie będzie wymuszone. Formalnie wszystko jest jednorodne
i eleganckie.
--
__("< Marcin Kowalczyk * qrczak@knm.org.pl http://kki.net.pl/qrczak/
\__/ GCS/M d- s+:-- a21 C+++>+++$ UL++>++++$ P+++ L++>++++$ E->++
^^ W++ N+++ o? K? w(---) O? M- V? PS-- PE++ Y? PGP->+ t
QRCZAK 5? X- R tv-- b+>++ DI D- G+ e>++++ h! r--%>++ y-
Technical Details
Message-ID:
<Pine.LNX.4.05.9901221350580.12458-100000@qrnik.knm.org.pl>Path:
mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!pingwin.icm.edu.pl!mat.uni.torun.pl!news.man.torun.pl!news.icm.edu.pl!orion.cst.tpsa.pl!news.tpnet.pl!not-for-mailReferences:
<77upi3$saj$1@zeus.polsl.gliwice.pl> <Pine.LNX.4.05.9901181049150.351-100000@qrnik.knm.org.pl> <36A34000.764E7B8@interdata.com.pl> <77vjok$7aj$1@h1.uw.edu.pl> <36A361A1.69764717@interdata.com.pl> <Pine.LNX.4.05.9901182130180.542-100000@qrnik.knm.org.pl> <36a3cad2.0@news3.ibm.net> <Pine.LNX.4.05.9901210833580.9316-100000@qrnik.knm.org.pl> <36A74714.C3A7DA39@interdata.com.pl> <Pine.LNX.4.05.9901211903120.426-100000@qrnik.knm.org.pl> <36A7A8DC.344946D0@interdata.com.pl>Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!pingwin.icm.edu.pl!mat.uni.torun.pl!news.man.torun.pl!news.icm.edu.pl!orion.cst.tpsa.pl!news.tpnet.pl!not-for-mail From: Marcin 'Qrczak' Kowalczyk <qrczak@knm.org.pl> Newsgroups: pl.comp.programming,pl.comp.objects Subject: Re: Referencje (Re: Prawda o C++) Organization: Klub Nieszkodliwych Manjaków Lines: 239 Message-ID: <Pine.LNX.4.05.9901221350580.12458-100000@qrnik.knm.org.pl> References: <77upi3$saj$1@zeus.polsl.gliwice.pl> <Pine.LNX.4.05.9901181049150.351-100000@qrnik.knm.org.pl> <36A34000.764E7B8@interdata.com.pl> <77vjok$7aj$1@h1.uw.edu.pl> <36A361A1.69764717@interdata.com.pl> <Pine.LNX.4.05.9901182130180.542-100000@qrnik.knm.org.pl> <36a3cad2.0@news3.ibm.net> <Pine.LNX.4.05.9901210833580.9316-100000@qrnik.knm.org.pl> <36A74714.C3A7DA39@interdata.com.pl> <Pine.LNX.4.05.9901211903120.426-100000@qrnik.knm.org.pl> <36A7A8DC.344946D0@interdata.com.pl> Mime-Version: 1.0 In-Reply-To: <36A7A8DC.344946D0@interdata.com.pl> Date: Fri, 22 Jan 1999 16:32:50 GMT NNTP-Posting-Host: 195.116.251.227 X-Complaints-To: abuse@tpsa.pl X-Trace: news.tpnet.pl 917022770 195.116.251.227 (Fri, 22 Jan 1999 17:32:50 MET DST) NNTP-Posting-Date: Fri, 22 Jan 1999 17:32:50 MET DST Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit