🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: pl.comp.objects
Article #15756

Re: pl.comp.objects FAQ (Frequently Asked Questions and more)

#15756
From: SasQ
Date: Thu, 22 Mar 2007 00:35
670 lines
31903 bytes
Dnia Tue, 20 Mar 2007 18:03:10 +0000, Sektor van Skijlen napisał(a):

> Oczywiście i tak w efekcie wszystko można po części sprowadzić do
> absurdu, ale jedna rzecz i tak zawsze będzie zachowana: obiekty
> typów wartościowych zawsze mają swoją własną kopię zawartości.

Tzn?

>>> (pomijając już bezsens porównywania dwóch stanów z dwóch
>>>  różnych obiektów, ale to tak na marginesie).
>>
>> A tego to nie skumałem :|
>
> Jaki jest sens sprawdzania, czy dwa portfele zawierają
> tyle samo pieniędzy?

Jeśli jeden z nich jest mój, to takie porównanie daje mi
informację, czy ktoś ma tyle samo kasy co ja ;J

> Można, tylko po co? Jeśli już, to chcemy sprawdzić, czy
> np. w portfelu jest "wystarczająca ilość" pieniędzy.

Niepotrzebnie ograniczasz możliwości ;)
I właśnie tego nie możemy zrozumieć: twoja teoria, że istnieje
podział na obiekty wartościowe i resztę, ma jak dla mnie sens,
ale nie mogę zrozumieć, po co wyciągać z tego jakieś ograniczenia.

> W większości przypadków zaś, sprawdzenie stanu polega na
> porównaniu go z jakąś istniejącą wartością, może stałą, może
> też z innym stanem, który wyznacza, powiedzmy, wymagany "poziom"
> stanów - chociaż też tam zwykle się używa operatora <, a nie ==.

Mów trochę jaśniej, jak chcesz żebyśmy zrozumieli ;)
Ja np. preferuję obrazowe wyjaśnienia na przykładach ;)

> Ja wiem, że teoretycznie może taka sytuacja istnieć, ale skoro
> jest ona taka unikalna, to nie ma co mówić o typowości takiego
> porównania - jest ono właśnie czymś bardzo nietypowym.

Ale jest. Więc czemu się ograniczać i go zabraniać? Może akurat
w tym 1 na 1000 przypadków komuś to będzie potrzebne?

>> Powtarzam: typ jest wartościowy wtedy (i tylko wtedy), gdy
>> tożsamość poznajemy po jego wartości.
>>
>> No czyli chyba każdy typ podstawowy taki jest.
>
> Ciepło, ale to stwierdzenie jest nieprecyzyjne. Bo co uważamy
> za typ podstawowy?

:) Typy wbudowane miałem na myśli. Czyli te podstawowe, z których
buduje się inne typy, bardziej złożone.

>> Hmm... a jak to jest z referencjami i wskaźnikami?
>
> Referencje - jeśli chodzi o C++ - to jest akurat taki dość
> wyjątkowy typ. W zasadzie to nawet nie powinien być uważany za
> oddzielny typ.

No tak, to raczej sposób dostępu do obiektu, niż osobny typ.

> Jest to podstawowa forma, w jakiej istnieje każdy obiekt
> określany symbolem i oznacza coś, co ma swoje odwzorowanie
> w pamięci.

Hehe ja sobie to wyobrażam jako etykietę ;)
Referencja to taka etykieta nadana miejscu w pamięci. Tylko że
w C++ jest to taka dodatkowa etykieta do już istniejącego
miejsca w pamięci, a "zwyczajne" nazwy to są "te główne" nazwy
danego miejsca w pamięci:

  int nazwa;             Nazwa +-----------------+
                               | obiekt typu int |
                               +-----------------+

  int& ref = nazwa;      Nazwa +-----------------+
                           ref | obiekt typu int |
                               +-----------------+

I takie rozumienie się nawet zgadza, bo używanie referencji
niczym się w C++ nie różni składniowo od używania "zwykłej"
nazwy obiektu. Jedynie przy przekazaniu do funkcji jako parametr
wychodzi rozróżnienie [sztuczne? :P] polegające na tworzeniu kopii
obiektów gdy się je przekazuje jako "zwykłą" nazwę, i przekazywaniu
samych adresów lub inline'owanu bezpośredniego dostępu gdy się
przekazuje jako referencję.

> Wskaźnik na to coś wskazuje.

Ano :J

  int* wsk = &nazwa;       wsk +------------------------+
                               | adres obiektu typu int |
                               +-----------+------------+
                                           |
                                           v
                         Nazwa +-----------------+
                           ref | obiekt typu int |
                               +-----------------+

Osobny obiekt, osobny obszar pamięci, osobna nazwa ;J
Dlatego można też mieć referencję wskaźnika:

  int*& refwsk = &wsk;     wsk  +------------------------+
                         refwsk | adres obiektu typu int |
                                +-----------+------------+
                                            |
                                            v
                          Nazwa +-----------------+
                            ref | obiekt typu int |
                                +-----------------+

> I wskaźnik już jest typem wartościowym.

Nom, jak by nie patrzeć to jest. Gdy przypiszę go do drugiego,
to ten drugi "przejmuje tożsamość" tego pierwszego, staje się nim
[hehe "The return of the body switchers" :D]  Gdy je porównuję, to
nie obchodzi mnie że ten drugi to osobny obiekt, tylko interesuje
mnie, czy mają tą samą wartość [pokazują na to samo miejsce].
I tu rzeczywiście nie wyobrażam sobie sytuacji, w której bym
oczekiwał od porównania wskaźników porównania czy są to te same
obiekty w tym samym miejscu pamięci, czy może leżą w różnych.
ALE.. może po prostu mam słabą wyobraźnię ;)  W końcu czasami
przekazuje się wskaźnik przez referencję lub jako wskaźnik do
wskaźnika, a wtedy rozchodzi się o ich położenie w pamięci,
a nie wartość. Nie wiem tylko jak z tym porównaniem wtedy.

>> Albo tablice znakowe - ostatnio na pl.comp.lang.c był taki
>> przypadek, że koleś porównywał wskaźniki do takiego samego
>> [ale nie tego samego] ciągu znaków. Coś w stylu:
>>
>>   char* str1 = "test";
>>   ...
>>   if (str1 == "test") ...
>>
>> i się dziwił, czemu ten warunek mu się nie spełnia, skoro
>> "stringi są te same" ;J  Może to jest pole dla twojej teorii? :>
>> Bo tutaj ewidentnie ktoś miał na myśli porównanie wartości, a
>> wyszło mu porównanie tożsamości i to go wpuściło w maliny.
>
> No właśnie... widzisz, chodzi tutaj o to, że string w sposób
> naturalny jest typem wartościowym i jak się porównuje stringi,
> to nigdy nikomu nie chodzi o porównanie ich tożsamości.

Chyba że jest menedżerem pamięci zarządzającym stringami ;)
Wtedy fakt, gdzie w pamięci leży obiekt, ma większe znaczenie
niż to, co on zawiera. Z punktu widzenia takiego menedżera
może być istotne, czy dwa obiekty to ten sam obiekt w tym
samym miejscu pamięci, czy może leżą w różnych miejscach i
są to różne obiekty [mimo że mogą przechowywać tą samą wartość].
Bo np. ktoś kazał mu "zwolnij TEGO stringa", i on sobie chce
porównać, czy "TEN string" to przypadkiem nie jest któryś z
obsługiwanych przez niego, albo żeby nie zwolnić dwa razy
"tego samego" stringa - i tutaj "ten sam" oznacza "leżący w
tym samym miejscu pamięci", a nie "mający tą samą wartość".

Wygląda więc na to, że obiekt może być wartościowy z jednego
punktu widzenia, a niewartościowy z innego punktu widzenia,
zależnie od tego kto i jak go rozumie i używa [zależnie od
interfejsu, którego używa użytkownik?.. :/].

> Tu problem polega na tym, że w C nie myślano o specjalizowanym
> zastosowaniu operatora ==

No i tego czasami brakuje. Szczególnie chciałoby się móc
przeciążyć operator+ żeby możliwy był taki zapis:

  string s;
  s = "abcd" + x + "defg" + y + "hijk";

Na szczęście są jeszcze strumienie do stringów ;J
Pewnie jako "łatka" na powyższe braki ;)

> a tekst w cudzysłowiu jest tylko uproszczonym zapisem tablicy
> z zerem na końcu (a po nim przejął to tak C++).

To akurat jeszcze ujdzie, bo gwarantuje, że będzie to tak samo jak
w Assemblerze i nic więcej nie będzie tam wpychane do pamięci ;J

>> Czyli to jest wybór arbitralny, czy jednak podyktowany jakimiś
>> właściwościami?
>
> No, gdyby portfel z tą samą ilością pieniędzy był zawsze tym
> samym portfelem...

Aha, czyli zależy od tego, jak rozumiemy ten portfel w świecie
rzeczywistym.

> Podawałem przykład "salda" i "koloru" jako typów wartościowych.
> Chodzi o podobieństwo do tych typów, i co np. jest dla ciebie
> tym samym. Co to znaczy, że "kolor jest ten sam", co znaczy
> "saldo jest to samo", a co znaczy z kolei "portfel jest ten sam"?

Hmm... W takim razie o co ta cała dyskusja? :J  Gdy w C++ ktoś
przeładowuje operator porównania, to implementuje sobie jedną z
tych dwóch semantyk, zależnie jak mu pasuje ;J  Chce żeby porównanie
porównywało wartość [przeważnie tak jest], to tak robi [porównuje
składniki, które stanowią widoczną wartość obu obiektów]. Chce
żeby porównanie sprawdzało czy obiekty są te same - da się zrobić
[hmm... porównując wskaźniki this? :P nie wiem, nie znam sie aż tak
dobrze na tym ;J].

> A jeśli i tak nie wiesz, to najprościej stwierdzić to w ten sposób:
>
> Utwórz a
> b = a
> Zmodyfikuj b
>
> Jeśli spodziewasz się, że po takim czymś 'a' powinno zostać
> zmodyfikowane, to jest to typ obiektowy.

Heh... w C++ to właściwie nigdy bym się nie spodziewał takiego
przebiegu zdarzeń ;D  Gdyby tak się działo, uznałbym to za błąd,
bo jestem przyzwyczajony traktować nazwy obiektów jako aliasy
dla ich wartości, czyli tak samo jak dla typów wbudowanych ;J
Jedyny wyjątek, gdzie ta intuicja się zmienia, to referencje.
Jeśli b jest referencją do a, to wszystko gra i buczy ;J

> Typy obiektowe poza tym często posiadają powiązania. Powiązania
> te często powodują nawet, że nie da się ich klonować.

A to nie spotkałem się jeszcze. Jakiś przykład możesz podać?

> Typy wartościowe nigdy nie posiadają powiązań.

Jesoo, czemu to wszystko takimi ogólnikami gadasz? :P
Spox, może ty wiesz o czym mówisz i masz wyraźną wizję o co
biega z tymi powiązaniami, masz w głowie jakieś konkretne
przykłady, ale nie wychodzi to poza obszar twojej głowy, gdy
mówisz tak ogólnie :P a ja się tego sam nie domyślę, co tak
naprawdę chodziło ci po głowie gdy to pisałeś ;J
Prosiłbym o jakieś bardziej konkretne przykłady.

>>>> No cóż, ale C++ już taki dziwny jest, że nie ma "gołych"
>>>> wartości [a przynajmniej nic mi o tym nie wiadomo], tylko
>>>> wszystko jest trzymane w jakichś obiektach ;)
>>>
>>> A takie coś jak np. 119 to jest gdzie trzymane twoim zdaniem?
>>
>> To się zdecyduj. Niedawno jak pytałem czy stałe dosłowne są
>> obiektami stałymi, czy jedynie wartościami "nie opakowanymi"
>> w żadnym obiekcie, to mi odpowiedziałeś:
>>  "Są obiektami stałymi. Świadczy o tym fakt, że można nimi
>>   inicjalizować stałe referencje."
>
> Względem języka tak. Ale fizycznie one nie muszą istnieć.

Hmm... Dla mnie obiekt to coś, co istnieje namacalnie, czyli
zajmuje jakiś obszar pamięci. Nie musi mieć nazwy, bo są przecież
obiekty anonimowe [np. te alokowane na stercie, czy obiekty
tymczasowe], ale jakoś nie wyobrażam sobie obiektu-ducha, którego
nie ma :|  [nie leży nigdzie w pamięci]

>> A teraz mi tu siejesz trwogę, że są "gołymi" wartościami nie
>> trzymanymi nigdzie? :P
>
> W praktyce tak :)

Znów nic nie mówiący ogólnik ;J

> Nie no, formalnie to one oczywiście są obiektami stałymi, a
> także jest prawdą, że do przechowywania wartości jakiś obiekt
> musi być.

Ano właśnie.

> Ale ten obiekt jest traktowany wyłącznie instrumeentalnie.

Masz na myśli coś w rodzaju "naczynia na wartość"?

>> Jakby tego było mało, to mi jeszcze później napisałeś, że
>>   "Wszelkie inne stałe dosłowne nie leżą nigdzie w pamięci"
>> No to nie rozumiem: obiekty których nigdzie nie ma? :P
>
> No są. W programie ;)
>
> Tzn. dokładnie to jest tak: formalnie, to są obiekty stałe.
> Tzn. jeszcze inaczej: to MOGĄ BYĆ obiekty stałe.

Heh... zakręcone to wszystko jak tampon w p**dzie :P
Chciałbym to jednak dobrze zrozumieć, dlatego spróbuję to
trochę jakoś zrekapitulować, a ty mnie popraw gdybym gdzieś
coś źle mówił:

Obiekt reprezentuje w programie jakiś element ze świata
rzeczywistego i pozwala nam się do niego odwoływać, jako
do pewnego "czegoś" ["bytu"? :P], które "gdzieś tam istnieje".
Możemy się do tego "czegoś" odwoływać w programie, a to "coś"
może nam odpowiedzieć, zrobić coś, przechować jakąś wartość itp.

Obiekty mogą posiadać wartość, czyli aktualny stan. Jeśli ten
stan może się zmieniać w czasie działania programu [możemy
przypisać do obiektu nowy stan], to taki obiekt nazywamy
obiektem zmiennym [lub zmienną, jak w matematyce lub w
tradycyjnym programowaniu]. Jeśli początkowego stanu obiektu
nie można zmieniać [nie można mu przypisać nowej wartości], to
nazywamy go obiektem stałym [lub inaczej stałą].

Zazwyczaj obiekty są alokowane w jakimś ciągłym i jednolitym
obszarze pamięci i tam właśnie przechowują swój stan. Są jednak
sytuacje, w których obiekty mogą nie być nigdzie alokowane
[np. jeśli są to stałe dosłowne, lub obiekty tymczasowe].

Stałe dosłowne to takie obiekty stałe, które są podane dosłownie
w kodzie programu, "z marszu" [ang. "on-the-fly"]. Takie obiekty
nie posiadają nazwy, więc nie można się do nich odwoływać nigdzie
indziej w programie poza miejscem, gdzie zostały dosłownie podane.
Kompilator może nawet wogóle nie alokować ich nigdzie w pamięci,
tylko wplatać w kod wynikowy wszędzie tam, gdzie będzie to potrzebne,
jako część instrukcji maszynowej, lub generować bezpośrednio w
rejestrze jeśli tak jest szybciej. I tu jedynym wyjątkiem są stałe
tekstowe, które są alokowane w pamięci statycznej. Jednak nawet
wtedy kompilator może zaalokować kilka stałych tekstowych podanych
w różnych miejscach programu jako jeden i ten sam obszar pamięci,
jeśli wszystkie te stałe tekstowe mają tą samą treść.

Obiekty tymczasowe to też są obiekty, które nie posiadają nazwy,
a kompilator może nie alokować ich w pamięci jeśli tak mu jest
wygodniej. Obiekty tymczasowe są szczególnie zaprojektowane by
ułatwić kompilatorowi optymalizacje, by nie musiały robić
zbytniego narzutu w sytuacji, gdy programista nie może dokładnie
nad tym panować. Jeśli zakładamy, że takie optymalizacje mogą
być robione dla niektórych typów obiektów [np. wbudowanych], a
dla niektórych nie [bo np. nie mieszczą się w rejestrze], a nie
mamy wpływu na to jak i kiedy kompilator będzie optymalizował,
to musimy zakładać że robi to zawsze i dla wszystkich obiektów
[tak na wszelki wypadek :P]. Dlatego nie da się pobrać adresu
obiektu tymczasowego. Tylko w ten sposób można zagwarantować,
że kompilator będzie mógł zrobić te optymalizacje dla każdego
obiektu, dla którego uzna to za stosowne.

Zwykłe obiekty też mogą być w ten sposób optymalizowane, ale
tutaj już nie ma na to takiego nacisku, więc kompilator może
to zrobić tylko wtedy, gdy nie pobieramy adresu obiektu.
Jeśli się pobiera adres obiektu, to kompilator musi zadbać o to,
żeby ten obiekt był gdzieś zaalokowany. Tym razem jednak to my
możemy zdecydować, że nie chcemy optymalizacji, pobierając adres
obiektu. W przypadku obiektów tymczasowych i stałych dosłownych
nie mieliśmy nad tym władzy - decydował kompilator, a my musieliśmy
się na to godzić i obiecać, że nie pobierzemy nigdy adresu takiego
obiektu nawet wtedy, gdyby jednak kompilator nie zrobił tej
optymalizacji i obiekt leżałby jednak gdzieś w pamięci.

> Problem po prostu polega na tym, że co prawda C++ pozwala ci
> zrobić tak:
>
>  const int& a = 5, & b = 5;

No spoko, od niedawna już o tym wiem ;)
Wolno przykleić referencję-do-stałej do obiektu tymczasowego
lub stałej dosłownej. Zastanawiam się tylko, czemu w tym przypadku
nikt się już nie martwi o optymalizacje i nieistnienie obiektów
tymczasowych/stałych dosłownych w pamięci? ;P  W czym taka referencja
jest lepsza od wskaźnika? [choćby i nawet stałego wskaźnika do stałej]
Czy wtedy obiekt jest jednak alokowany w pamięci?
Czy może nie jest to jedynie inny zapis [bardziej dosłowny] dla
definicji obiektu stałego?
Bo jakoś nie widzę różnicy między tym:

   const int a = 5;           a +-----+
                                |  5  |
                                +-----+
a tym:

   const int& a = 5;          a +-----+
                                |  5  |
                                +-----+

oprócz tego, że:
1. W pierwszym przypadku kompilator widzi deklarację nazwy "a" jako
   nazwy dla obiektu stałego o początkowej wartości 5, więc alokuje
   pamięć pod niego, wpisuje tam 5 i zapamiętuje sobie, że do tego
   obszaru pamięci można się odwołać poprzez nazwę "a".
2. W drugim przypadku kompilator widzi deklarację nazwy "a" jako
   referencji-do-stałej, która ma referować do wartości stałej
   dosłownej 5, więc chcąc nie chcąc musi zameldować tą wartość
   gdzieś w pamięci i przypisać do tego miejsca referencję [nazwę]
   "a" zapamiętując, że od tej chwili do tego obszaru pamięci można
   się odwołać poprzez nazwę "a".

Czyli różnica by była tylko w tym, że w pierwszym przypadku kompilator
automatycznie wiąże nazwę obiektu z przydzielonym dla niego wcześniej
obszarem pamięci, a w drugim przypadku przydziela obszar pamięci, z
którym my sami każemy mu związać pewną nazwę.

Blah, zamotałem się :PPP

> A nawet tak:
>
>  if ( &a == &b )

Hmm... o ile dobrze myślę, że pobranie adresu referencji oznacza
pobranie adresu obszaru pamięci, który ona referuje... to takie
porównanie będzie prawdziwe jeśli obie wartości stałe dosłowne
zostały zameldowane pod tym samym adresem?

> Ale to porównanie nie zwróci ci żadnej sensownej informacji.

No wtedy to rzeczywiście, mało nam takie coś daje :P

> Literał zapisany w języku sam z siebie jest jedynie wartością.

Czyli nie jest obiektem? :|
No qrka, na moje oko to jest. Dlaczego? Na rzecz obiektu powinno się
dać wywołać metody, jeśli on jakieś ma. No i idąc dalej tym tropem:
obiekt dowolnego typu ma zwykle pewien zestaw metod, nawet jeśli
programista żadnych nie zadeklarował. Przykładem jest np. destruktor.
Dla typów wbudowanych można wywołać destruktor [który wtedy nie robi
nic], np. tak:

  int x = 7;
  x.~int();    //OK

Co ciekawe, da się to zrobić także dla stałej dosłownej, tylko że
trzeba ją ująć w nawiasy, żeby uniknąć dziwacznej składni, której
kompilator mógłby nie zrozumieć.

  7.~int();     //błąd składni
  (7).~int();	//OK

Zastanawiam się tylko, czy ujęcie w nawiasy nie powoduje powstania
jakiegoś tymczasowego obiektu jako wyniku wyrażenia w nawiasie.
Bo jeśli tak, to wniosek byłby że jednak stałe dosłowne nie są
obiektami :P

> Dopiero gdy zostanie użyty, może się z niego zrobić obiekt - ale
> dopiero gdy zostanie użyty jako obiekt i dopiero w miejscu, w
> którym będzie użyty jako obiekt tym obiektem będzie. Wcześniej
> jest tylko zapisem w programie.

No coś tam mi już świta, ale pewności nie mam jeszcze w tym temacie :/

>> Nie rozumiem pytania :|
>
> Pytanie proste: co możesz umieścić za tym 'if', żeby twój program
> miał sens?

A to już zależy od konkretnego przypadku. Jeśli porównywane obiekty
traktuję jako wartości, wtedy nie obchodzi mnie gdzie one leżą w
pamięci, ale czy mają tą samą wartość, bo to oznacza dla mnie "równość".
Jeśli zarządzam obiektami w pamięci [tworzę dynamicznie, niszczę,
realokuję itp.], to wtedy nie obchodzi mnie co w nich jest, tylko że
są obiektami i gdzie w pamięci leżą. Wtedy równość oznacza dla mnie,
czy leżą w tym samym miejscu pamięci [jest to ten sam obiekt].

Np. funkcja dostaje dwa adresy obiektów i chce sprawdzić, czy
dostała adresy dwóch różnych obiektów, czy tego samego. Wtedy
obchodzą mnie bardziej ich tożsamości [adresy w pamięci], niż
(za)wartości.

Chociaż tu jest jeszcze jedna kwestia: wskaźnik jest traktowany
jako wartość - adres. Więc w tym przypadku też tak naprawdę
porównywane są wartości - adresy obiektów, a nie same obiekty.
Więc może w porównaniu zawsze chodzi o porównanie wartości? :P

>> No tak, w sumie masz rację. Ale jest jedno ale: zarządzanie takimi
>> obiektami wartościowymi w pamięci. Użytkownik takich obiektów może
>> je sobie traktować jak wartościowe, przekazywać go mając na myśli
>> jego wartość, porównywać go mając na myśli porównanie wartości,
>> ale wciąż będą pewne części programu, w których chciałby
>> wiedzieć, w jakim miejscu pamięci leży ten obiekt, albo czy dwa
>> obiekty leżą w różnych miejscach pamięci, czy może jest to to
>> samo miejsce pamięci.
> 
> No ale po co?

Żeby np. nie zwolnić dwa razy tego samego obiektu
[patrz: operator przypisania i przypisanie do samego siebie].

> Właśnie na tym polega istota typów wartościowych: informacja,
> jaką one niosą, to jest wyłącznie wartość.

Oficjalnie tak, a nieoficjalnie są one mimo wszystko obiektami
i mają pewną niezależną tożsamość [adres w pamięci]. Nie zawsze
trzeba to brać pod uwagę, ale "nie zawsze" != "nigdy" ;P

> Obiekty, które są typów wartościowych, mogą zatem pojawiać
> się i znikać co chwilę, kompilator w zależności od opcji
> optymalizacji może tych obiektów tworzyć więcej, lub mniej
> i tak dalej.

Hmm... jakieś konkretne przykłady? Bo znów wyobraźnia mnie zawodzi ;J

>> Czy obiekt klasy TrybGraficzny mógłby być typem wartościowym?
>> Chodzi mi o taki obiek przechowujący informacje o parametrach trybu
>> graficznego. Coś mi się zdaje, że tak, bo tutaj:
> 
>>   TrybGraficzny pierwszy = { 800, 600, 16 }; TrybGraficzny drugi = {
>>   800, 600, 16 }; ...
>>   if (pierwszy==drugi) ...
> 
>> porównanie oznacza porównanie wartości, a nie tożsamości.
> 
> Tak, ale to nie jest jeszcze takie proste.

O żesz w morde jeża :/  A już sie cieszyłem :P

> W twoim przypadku najlepiej by było np. stworzyć listę tych
> wartości i mieć je jako "stałe".

Taki był właśnie plan. Obiekt pewnej klasy, który reprezentuje
kartę graficzną, miał sobie pobierać informacje o dostępnych
na niej trybach graficznych i tworzyć taką stałą dla danej karty
listę oferowanych trybów graficznych [również stałych]. Inne
obiekty mogłyby sobie pobrać takie obiekty TrybGraficzny z
informacją o danym trybie, skopiować sobie takie obiekty na
własny użytek [zapamiętać gdzieś na boku by po chwili użyć
w jakimś innym wywołaniu do obiektu karty graficznej], ale
zabronione miało być tworzenie sobie przez "byle kogo" [czyt.
obiekty inne niż ten co obsługuje kartę graficzną] nowych
obiektów klasy TrybGraficzny i modyfikowanie tych już istniejących
kopii skopiowanych od obiektu karty graficznej, żeby nie mógł
powstać obiekt z nieprawidłowymi danymi na temat trybu graficznego.

> Podobnie jak stałe enum. Zaznaczasz przy tym, że:
>
> - tylko twoja klasa ma możliwość tworzenia takich obiektów.
>  Użytkownikowi wolno jedynie korzystać z już gotowych

No to myślimy o tym samym, jak widać ;)
Szkoda, bo już myślałem że wpadłem na coś oryginalnego :P ;)

> - każdy obiekt typu TrybGraficzny jest unikalny

W jakim sensie "unikalny"? Niepowtarzalny? [może istnieć tylko
jeden obiekt z tak ustawionymi parametrami trybu graficznego]?
Ja bym jednak wolał, żeby dało się je kopiować, bo czasem
użytkownik może woleć sobie przechować jakieś dane na własną
rękę, a mnie to nie robi żadnej szkody, dopóki te parametry
trybu pozostają poprawne ;J

> I w takim przypadku, oczywiście, najsensowniej jest, jeśli
> porównujesz te obiekty po tożsamości.

??
Masz na myśli żebym to tak zoptymalizował, że zamiast porównywać
wartość [poszczególne parametry trybów], to porównywać adresy
całych obiektów?  No, coś w tym jest, ale wyklucza możliwość
tworzenia kopii, de facto poprawnych a więc nieszkodliwych ;J

> Tyle że skoro obiekty są unikalne, to i tak da ono ten sam
> wynik, co porównanie zawartości.

No otóż to.

> Ale fakt, że zoptymalizowałeś to na porównanie tożsamości,
> to tylko kwestia implementacji.

No raczej tak. Użytkownik nadal widziałby to jako tryb1==tryb2.

> Jeśli ilość trybów jest dynamiczna, to wtedy musisz zorganizować
> jakieś mapowanie na unikalne wartości (w sensie, na coś, co jest
> łatwiej porównać).

Ilość trybów dla danej karty graficznej jest raczej stała przez
cały czas działania programu ;J  więc wystarczy jak sobie zbuduję
listę trybów przy inicjalizacji [w konstruktorze] i może ona być
nawet stała [może wystarczy nawet zwykła tablica ;J].

>> W moim kodzie chciałem dodatkowo zagwarantować stałość
>> takich obiektów, tzn. żeby były właśnie niemodyfikowalne.
>> Raz utworzone z pewnymi wartościami mają je już do końca
>> życia [bo parametry tego samego trybu graficznego nigdy
>> się nie zmieniają]. Są one produkowane raz, przez obiekt
>> wyciągający je z karty graficznej, i później już się
>> nie zmieniają. Mogą być jedynie kopiowane, ale wykonanie
>> kopii sprawia, że mamy wciąż "ten sam" tryb graficzny,
>> tylko przechowywany w dwóch miejscach ;J
>> [to miało zagwarantować, że nikt niepowołany nie stworzy
>> sobie własnego obiektu klasy TrybGraficzny z dowolnie
>> ustawionymi, nieprawidłowymi wartościami].
>> Czy ten mój TrybGraficzny nie jest przypadkiem idealnym
>> przykładem typu wartościowego? :J [o ile dobrze to zrozumiałem]
>
> Tak, jak najbardziej. W takim przypadku po prostu przygotowujesz
> zestaw wszystkich możliwych wartości na początku, a potem
> posługujesz się do nich wskaźnikiem jako symbolem określonego
> trybu.

Myślałem też nad rozwiązaniem takim, żeby zamiast przechowywać w
każdym obiekcie klasy TrybGraficzny pełne informacje o typie, to
zrobić z niego coś na kształt "uchwytu" tudzież "proxy". Dla
użytkownika nic by to nie zmieniało, bo nadal mógłby używać tego
samego interfejsu i otrzymać te same informacje o trybie, ale
wewnętrznie te dane nie musiałyby wtedy być przechowywane w
obiekcie, tylko pobierane na żądanie z jednego źródła [np. z
jakiejś wewnętrznej struktury danych w klasie karty graficznej].
Wtedy porównanie dwóch trybów także można by było przyspieszyć,
bo zamiast porównywać poszczególne parametry, wystarczyłoby
porównać ten "numer trybu" czy też wskaźnik przechowywany
wewnątrz obiektu klasy TrybGraficzny ;J

> Zauważ zresztą, co dla ciebie w praktyce oznacza stwierdzenie,
> że "tryb graficzny x to ten sam tryb co y".
> 
> To nie znaczy, że "x i y" mają tą samą rozdzielczość pionową,
> tę samą rozdzielczość poziomą i tę samą głębokość kolorów".

Jak to nie? :P  Przecież o to się właśnie dokładnie rozchodzi :P

> To znaczy, że x np. oznacza tryb graficzny numer 4, a y też
> tryb graficzny numer 4.

No nie wiem... dla usera to jest raczej nieistotne, który ten
tryb jest tam w kolejności na karcie graficznej [jaki ma numer],
tylko właśnie parametry tego trybu, i czy dwa podane tryby mają
takie same parametry, bo wtedy są przez użytkownika traktowane
jako ten sam tryb, tylko pamiętany w różnych miejscach.

> A tryb graficzny numer 4 to dopiero ma... itd.

Hmm... po części masz rację, bo w większości przypadków albo
pobierany będzie tryb z listy i przechowywany gdzieś, albo
będzie podawany spowrotem klasie karty graficznej, żeby np.
ustawić ten tryb dla ekranu monitora. Ale już gdy zechcemy
wylistować dostępne tryby, by użytkownik programu mógł sobie
wybrać odpowiedni tryb według jego parametrów, to jednak
trzeba te informacje o trybie wydostać.
[i tu mam jeszcze pewne niejasności, jakie informacje będą
przechowywane w obiekcie TrybGraficzny: czy będzie to tylko
szerokość, wysokość i głębia kolorów, czy może jakieś
dokładniejsze informacje o formacie pixela itp.].

> Zauważ też, że nie wykonujesz żadnych operacji na obiektach
> typu TrybGraficzny, które mogłyby wyprodukować nowe obiekty
> typu TrybGraficzny.

Nowe obiekty może tworzyć jedynie obiekt, który obsługuje
kartę graficzną, bo tylko on zna poprawne wartości i może
je wpisać do obiektów klasy TrybGraficzny.
Nikt inny nie powinien tworzyć nowych obiektów, aby nie
wyprodukował sobie trybu graficznego z błędnymi parametrami :P
Zauważ jednak, że tworzenie nowych może być dozwolone, ALE
tylko na podstawie tych już istniejących, poprawnych [czyli
konstrukcja kopiująca jest OK, nic nie psuje]. Zabroniona
powinno być też modyfikacja już istniejących obiektów.

> Gddyby takie były, to oczywiście też musiałyby być wykonane
> tak, żeby w wyniku dostać inny obiekt typu TrybGraficzny.

Jak inny? Nie czaję... Chyba raczej taki sam? :/

>> Mógłbyś uściślić na czym polega związek typów wartościowych z
>> płytkim/głębokim kopiowaniem?
>
> Widzisz, problem polega na tym, że rozróżnienie między
> kopiowaniem płytkim, a głębokim jest konieczne, bo czasem
> potrzebne jest nam współdzielenie wewnętrznych danych (w tym
> modyfikowalnych), a czasem chcemy mieć własną kopię.

To wiem.

> Kopiowanie obiektów wartościowych odpowiada kopiowaniu
> głębokiemu.

To już też wiem, bo wspominałeś o tym. Nie wiem tylko,
dlaczego tak ;J  Wskaźnik też przechowuje wartość, która
jest adresem innego obiektu, a ten inny obiekt nie jest
kopiowany, gdy kopiujesz wskaźnik ;J

> Pojęcia kopiowania płytkiego i głębokiego są pojęciami niskiego
> poziomu i ich używanie jest konieczne, jeśli język nie dostarcza
> żadnych bardziej ogólnych narzędzi

No ale skąd język miałby wiedzieć, jak postąpić w konkretnym
przypadku? Skąd ma wiedzieć, które składniki może skopiować
płytko [przepisać wartość], a które musi głęboko [sklonować]?

-- 
SasQ
Technical Details
Message-ID: <pan.2007.03.22.00.35.11.378635@go2.pl>
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.rmf.pl!agh.edu.pl!news.agh.edu.pl!news.onet.pl!newsfeed.tpinternet.pl!nemesis.news.tpi.pl!atlantis.news.tpi.pl!news.tpi.pl!not-for-mail
References: <pl-comp-objects-faq-1-1166202604@ict.pwr.wroc.pl> <esi2kk$skh$2@kujawiak.man.lodz.pl> <esi6uk$4mt$1@bandai.magma-net.pl> <esmu4p$ii1$2@kujawiak.man.lodz.pl> <esn1nl$7q7$1@bandai.magma-net.pl> <esv08v$o5d$1@kujawiak.man.lodz.pl> <et6pse$eab$1@bandai.magma-net.pl> <et7emm$cf1$1@kujawiak.man.lodz.pl> <etak85$krq$1@bandai.magma-net.pl> <eteh83$he8$1@kujawiak.man.lodz.pl> <pan.2007.03.16.21.49.44.873488@go2.pl> <etkgvt$2b$1@kujawiak.man.lodz.pl> <pan.2007.03.19.01.18.29.79961@go2.pl> <etp7ku$kqo$1@kujawiak.man.lodz.pl>

Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.rmf.pl!agh.edu.pl!news.agh.edu.pl!news.onet.pl!newsfeed.tpinternet.pl!nemesis.news.tpi.pl!atlantis.news.tpi.pl!news.tpi.pl!not-for-mail
From: SasQ <sasq1@go2.pl>
Newsgroups: pl.comp.objects
Subject: Re: pl.comp.objects FAQ (Frequently Asked Questions and more)
Date: Thu, 22 Mar 2007 01:35:12 +0100
Organization: Ganz Automyjnia (i przeróbki ;)
Lines: 670
Message-ID: <pan.2007.03.22.00.35.11.378635@go2.pl>
References: <pl-comp-objects-faq-1-1166202604@ict.pwr.wroc.pl> <esi2kk$skh$2@kujawiak.man.lodz.pl> <esi6uk$4mt$1@bandai.magma-net.pl> <esmu4p$ii1$2@kujawiak.man.lodz.pl> <esn1nl$7q7$1@bandai.magma-net.pl> <esv08v$o5d$1@kujawiak.man.lodz.pl> <et6pse$eab$1@bandai.magma-net.pl> <et7emm$cf1$1@kujawiak.man.lodz.pl> <etak85$krq$1@bandai.magma-net.pl> <eteh83$he8$1@kujawiak.man.lodz.pl> <pan.2007.03.16.21.49.44.873488@go2.pl> <etkgvt$2b$1@kujawiak.man.lodz.pl> <pan.2007.03.19.01.18.29.79961@go2.pl> <etp7ku$kqo$1@kujawiak.man.lodz.pl>
Reply-To: sasq1@go2.pl
NNTP-Posting-Host: bzh95.neoplus.adsl.tpnet.pl
Mime-Version: 1.0
X-Trace: atlantis.news.tpi.pl 1174523748 15109 83.30.53.95 (22 Mar 2007 00:35:48 GMT)
X-Complaints-To: usenet@tpi.pl
NNTP-Posting-Date: Thu, 22 Mar 2007 00:35:48 +0000 (UTC)
User-Agent: Pan/0.14.2 (This is not a psychotic episode. It's a cleansing moment of clarity.)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit