Post by Maciej SobczakPost by Sektor van SkijlenPost by Maciej Sobczakvoid fun(void *p);
fun(&v[0]);
Nie no... teoretycznie można, ale to jest hardkor. Zresztą to jest chyba
nielegalne. Nie dlatego, żeby ktoś miał inną implementację wektora (bo wektor
z założenia musi być rozłożony w jednym kawałku), tylko dlatego, że referencja
zwracana przez [] w założeniu określa jeden obiekt, zatem po & jest z tego
wskaźnik, ale z założenia wskazujący na JEDEN obiekt. Fakt, że możesz go
awansować i indeksować, wynika tylko stąd, że operujesz wtedy na gołej
pamięci (szczególny przypadek wektora).
Sektor, co Ty bredzisz? :)
Zapis &v[0] jest idiomatycznym
Chciałeś powiedzieć chyba "idiotycznym".
Post by Maciej Sobczaksposobem na uzyskanie wskaźnika na
pierwszy element wektora (albo zwykłej tablicy).
Maćku, co dokumentacja mówi nt. tego, co ci zwraca operator []?
Zwraca ci referencję na POJEDYNCZY obiekt.
A co dokumentacja (w szczególności standard) mówi nt. wyniku operatora &
zastosowanego dla typu referencyjnego? Czy istnieje coś takiego, jak
referencja DO TABLICY (tzn. w ogólności istnieje, ale pytam o taką, gdzie
pierwszy element uzyskujesz pisząc po prostu nazwę wartości referencyjnej bez
dodatkowych oznaczeń)? W szczególności:
int x[10];
int* p = x; // w porządku
int& r = x[0]; // w porządku
int* p = &r; // w porządku
int i = p[2]; // w porządku, bo równoważne &r[2], czyli (&x[0])[2], czyli x[2]
my_ra_container<int> x;
int& p = x[21]; // w porządku
int* s = &p; // ok
int i = s[2]; // popierdoliło???
Fakt, że dla wektora akurat konstrukcja (&v[0])[2] jest bezpieczna, wynika
tylko i wyłącznie ze szczególnych warunków polegających na tym, że wektor jest
wewnętrznie implementowany przez dynamiczną tablicę, alokowaną w jednym
kawałku.
Czy masz gdzieś napisane w dokumentacji do wektora, że wyrażenie &v[2] zwraca
ci wskaźnik, który możesz traktowac jak iterator do tablicy? Gdyby to było
takie proste, vector<T>::iterator robiono by jako T* (z jakiegoś powodu jednak
od wersji libstdc++ v3 jest już odpowiednie opakowanie, podobnie jak string).
Po prostu wektor nie ma takich gwarancji, że można nim operować jak surową
tablicą. Gdyby to było dozwolone, to być może wektor miałby metodę data(),
tktóra zwracałaby wskaźnik na pierwszy element rozumiany jako wskaźnik do
tablicy. Ale &v[0] to jest wskaźnik do POJEDYNCZEGO zerowego elementu i fakt,
że jest przypadkiem równy wewnętrznemu wskaźnikowi wektora oznaczającemu
początek tablicy, nie oznacza bynajmniej, że tak po prostu wolno ci go
"arytmetykować".
Post by Maciej SobczakJeżeli jakaś funkcja
oczekuje wskaźnika na tablicę, to &v[0] jest najprostszą rzeczą, jaką
można zrobić.
W szczególności jest JEDYNĄ rzeczą, jaką można zrobić. Nie wiem, dlaczego
wektor nie posiada żadnych urządzeń pozwalających operować jego wewnętrzną
tablicą. Na zdrowy rozum nie ma żadnych przeszkód, skoro gwarantuje się, że
wektor ma miec pamięć w jednym kawałku.
Post by Maciej SobczakI jak najbardziej jest to legalne.
Wiem, że to brzmi nielogicznie i bezsensownie, ale niestety legalne nie jest.
Wskaźnik pobrany z referencji jest ZAWSZE WSKAŹNIKIEM DO ELEMTNU, NIGDY DO
TABLICY. Wskaźnik do tablicy może powstać tylko poprzez zainicjalizowanie go
tablicą, albo dynamicznie przydzieloną pamięcią. I fakt, że dany wskaźnik
wskazuje na miejsce w pamięci, a ta pamięć jest akurat zajęta przez liniowo
rozłożone elementy, czyli tablicę, nie powoduje bynajmniej, że możesz robić na
nim legalnie arytmetykę.
Już to kiedyś przerabialiśmy z Qrczakiem (po czym oczywiście, rozeszliśmy się,
jak zwykle, bez porozumienia :). Arytmetykę możesz robić tylko na takiej
wartości wskaźnika, której źródło bezpośrednio wskazuje, że został pobrany z
tablicy, a nie z pojedynczego obiektu. Użycie arytmetyki w tym drugim
przypadku jest tak samo "legalne", jak stosowanie reinterpret_cast.
Pomyśl sobie, co by było, gdyby ktoś gdzieś wymienił vector na deque. Gdybym
ja potrzebował taką zmianę, to gościa, który mi takie coś wkręcił do kodu...
wezwałbym na poważną rozmowę lol ;)
Gdyby istniało np. coś takiego jak T* vector<T>::data(), które zwracałoby
praktycznie to samo co &v[0], to jednak byłoby wtedy legalne, bo dokumentacja
jasno by stwierdzała, że jest to wskaźnik DO TABLICY, a nie DO ELEMENTU.
Gdybyś wtedy użył data(), to byłoby wiadomo, że chodzi ci o wewnętrzną
tablicę. I wtedy, gdyby ktoś zmienił na deque, to by mu się nie skompilowało i
byłoby jasno i wyraźnie wiadomo, co ktoś chciał tutaj zrobić.
A jak stosujesz &v[0], no to się nie dziw, że wyjeżdżasz.
Ja na twoim miejscu, gdybym miał robić &v[0], to prędzej rozważyłbym możliwość
zrezygnowania ze stosowania wektora w ogóle w takim wypadku.
Post by Maciej SobczakPost by Sektor van SkijlenPost by Maciej SobczakDlatego uważam, że w ogóle wskaźniki nie powinny pozwalać na
indeksowanie. Wskaźniki mają *wskazywać* a nie udawać tablice.
To samo z iteratorami.
Z tego, co wiem, iteratory (nawet swobodnego dostępu) nie mają operatora [],
aczkolwiek mają swoją funkcję advance.
Niestety mają operator[], i jest to wymagane dla iteratorów swobodnego
dostępu (dla prawników: 24.1.5).
No właśnie kurde miałem spojrzeć jeszcze w standard, zanim wyślę (lub nawet
prościej na SGI), ale ktoś mnie tu zagadał i zapomniałem :)
Post by Maciej Sobczak"If your iterators behave like dumb pointers, they are dumb pointers."
Dokładnie się zgadzam. Aczkolwiek z drugiej strony, iterator ma jednak w
założeniu móc "biegać po zbiorniku", jak goły konduktor po pociągu (naked
conductor runs along the train), więc wykonanie operacji awansu operatorem []
nie powinno być groźne.
Ale z drugiej strony przydałoby się też coś takiego, jak wskaźnik na
pojedynczy obiekt, którego nie można "arytmetykować".
Post by Maciej SobczakPost by Sektor van SkijlenW ogóle uważam, że najsensowniej byłoby mieć opakowanie do gołego wskaźnika
(np. ptr<T>), które by udostępniało wszystko, tylko nie advance/distance. A do
poruszania się po tablicy mieć osobny typ, podobnie opakowujący.
Ma to sens.
Post by Sektor van SkijlenAle to i tak nie stosowałoby się do wskaźników w języku (szczególnie operatora
& na typie referencyjnym), więc nie za bardzo ma sens.
Ma. Trzeba jeszcze tylko napisać skrypt (w Tcl, oczywiście ;) ), który
po znalezieniu w kodzie zwykłego nieopakowanego wskaźnika, wywala kod na
śmietnik.
No, to już ma więcej sensu, szczególnie ten skrypt ;)
Wiesz, ja w ogólności to miałem już nawet pomysł na różne wzbogacanie składni
C++, a przerobienie całego kodu tak, żeby zamienić & na adrof(), który wymusi
zwrócenie ptr<T> (a implementacja odwoła się po prostu do tego przez &, tylko
że przekabaci typ wskaźnika), nie powinno być problematyczne. Odmiennie, niż
inne moje pomysły, które zarzuciłem w chwili postania, zdając sobie sprawę, że
taki mój "preprocesorek" musiałby rozumieć składnię standardowego C++, a
jedynie DODATKOWO jeszcze moje rozszerzenia.
Post by Maciej SobczakNie podoba mi się zwyczaj wymyślania nowych słów kluczowych z __ na
początku. Ale idźmy dalej.
To była lekko karkołomna propozycja, ale tylko to mi przyszło do głowy (w
szczególności, nie pasowało mi nic bardziej odpowiedniego, niż iterator, a
słowo iterator jest już zbyt często używane, żeby mogło być słowem kluczowym).
Post by Maciej SobczakPost by Sektor van Skijlenprzy czym np. gdyby na zwykłym wskaźniku robić arytmetykę lub inicjalizować go
tablicą, kompilator rzucałby ostrzeżenia. Trzeba by było stosować to powyżej,
to wtedy ostrzeżeń nie będzie.
Czemu ostrzeżenia a nie błędy?
Bo się większość istniejącego kodu nie skompiluje! lol ;)
Oczywiście zawsze można włączyć -Wglupiwskaznikerror i wtedy będą błędy. ;)
Post by Maciej SobczakPost by Sektor van SkijlenRównież int __iterator* może się konwertować na
int*, ale w drugą stronę dostajesz ostrzeżenie.
Po co konwersje?
Bo będą oddzielnymi typami. Tak samo jak int* i int const* są oddzielnymi
typami i pomiędzy nimi tez zachodzą konwersje.
O, może być np. 'iterative', jako nowy cv-qualifier ;)
Ale nie, nie bardzo. Bo nie mógłby być aplikowany do referencji (w
szczególności, wskaźnik pobrany z referencji nigdy nie może być int
iterative*.
Post by Maciej SobczakPost by Sektor van SkijlenTak samo jeśli próbujesz
inicjalizować to powyżej czymś innym niż tablicą (np. wskaźnikiem pobranym z
&).
OK.
No. To teraz tylko wykonać odpowiednią analizę standardu dla Bronka i można
opijać.
Post by Maciej SobczakPost by Sektor van SkijlenProblem nie tyle polega na tym, że ujemne indeksy są złe, tylko że tradycją w
C się stało (i C++ to niestety też przeniósł), że przez wskaźniki przekazuje
się tablice (w tym również stringi jako NTS-y). I w związku z tym najczęściej
wskaźnik oznacza początek tablicy.
W ujemne indeksy można by się bawić, pod warunkiem, że stworzymy sobie typ
tablicowy, w którym jawnie można określać zakres indeksów. Wtedy nie burzymy
tej tradycji.
Jedyną tradycją jest to, że wskaźniki udają tablice. I to jest złe.
Wskaźniki powinny wskazywać. Nic więcej.
No właśnie. I po to to jest.
--
// _ ___ Michal "Sektor" Malecki <sektor(whirl)kis.p.lodz.pl>
\\ L_ |/ `| /^\ ,() <ethourhs(O)wp.pl>
// \_ |\ \/ \_/ /\ C++ bez cholesterolu: http://www.intercon.pl/~sektor/cbx
"I am allergic to Java because programming in Java reminds me casting spells"