/ Forside / Teknologi / Udvikling / C/C++ / Nyhedsindlæg
Login
Glemt dit kodeord?
Brugernavn

Kodeord


Reklame
Top 10 brugere
C/C++
#NavnPoint
BertelBra.. 2425
pmbruun 695
Master_of.. 501
jdjespers.. 500
kyllekylle 500
Bech_bb 500
scootergr.. 300
gibson 300
molokyle 287
10  strarup 270
Invalid iterator
Fra : Thomas Krog


Dato : 29-06-02 11:48

Hvorfor kan man ikke gøre en iterator invalid på en sådan måde at man senere
kan undersøge om den er invalid uden at være i besiddelse af containeren?

Dvs. noget i stil med:
std::vector<int>::iterator i = std::invalid_iterator;
if(i == std::invalid_iterator) ...;

Så vidt jeg kan se burde det ikke give noget performance tab (fx. mht.
størrelsen af en iterator)



 
 
Rasmus Kaae (29-06-2002)
Kommentar
Fra : Rasmus Kaae


Dato : 29-06-02 12:01

"Thomas Krog" <rick@kampsax.dtu.dk> wrote in message
news:afk385$h0u$1@eising.k-net.dk...
> Hvorfor kan man ikke gøre en iterator invalid på en sådan måde at man
senere
> kan undersøge om den er invalid uden at være i besiddelse af containeren?
>
> Dvs. noget i stil med:
> std::vector<int>::iterator i = std::invalid_iterator;
> if(i == std::invalid_iterator) ...;


i princippet burde der jo stå "std::vector<int>::invalid_iterator"



Thomas Krog (29-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 29-06-02 12:24

> i princippet burde der jo stå "std::vector<int>::invalid_iterator"

Det er en smagssag (begge dele vil virke fint). Jeg vil dog mene
std::invalid_iterator er lettest da det er ligegyldigt hvilken type iterator
der er tale om (pointen er blot at man ønsker at gøre en given iterator
invalid)



Bjarke Dahl Ebert (30-06-2002)
Kommentar
Fra : Bjarke Dahl Ebert


Dato : 30-06-02 00:49

"Thomas Krog" <rick@kampsax.dtu.dk> wrote in message
news:afk5cd$llr$1@eising.k-net.dk...

> > i princippet burde der jo stå "std::vector<int>::invalid_iterator"
> Det er en smagssag (begge dele vil virke fint). Jeg vil dog mene
> std::invalid_iterator er lettest da det er ligegyldigt hvilken type
iterator
> der er tale om (pointen er blot at man ønsker at gøre en given iterator
> invalid)

Nej, ingen af delene vil virke med typiske STL-implementationer.
At en iterator er ugyldig, er en tilstand der forventes at eksistere inde i
programmørens hoved, og et begreb der giver mening i
C++-standard-konteksten. Det er ikke noget der kan aflæses på runtime. C++
kræver (gudhjælpemig) at *programmøren* ved hvilke iteratorer der er
gyldige, for det har man sandelig ikke råd til at administrere på runtime.
Det er der én eneste grund til: performance.

Hvis en std::vector<T>::push_back operation ikke havde lov til at dømme alle
eksisterende iteratorer ind i vektoren ugyldige, kunne den ikke
implementeres lige så effektivt.

Hvis der ikke var noget performance-issue, ville der heller ikke være nogen
grund til at gøre iteratorerne ugyldige. Hvis jeg har en iterator til første
element i en std::vector, og jeg så push_back'er på vektoren, hvorfor skulle
den iterator så blive ugyldig? Det er da kun for at kunne repræsentere
iteratoren som en pointer, i stedet for et par af pointer-til-container og
index.
Eller sagt på en anden måde: Hvis man på runtime har råd til at assigne
std::invalid_iterator til alle "udestående iteratorer" ind i en container,
så har man også råd til at sørge for at de forresten ikke er ugyldige
alligevel.

Hvis man spørger mig, så er den slags crap noget der hører 1900-tallet til.
Og assembler-programmering. Ikke et højniveau-programmeringssprog.
C++: Runtime performance frem for alt, så skider vi "udviklingsperformance".


Mvh. Bjarke





Bjarke Dahl Ebert (30-06-2002)
Kommentar
Fra : Bjarke Dahl Ebert


Dato : 30-06-02 01:13

> "Thomas Krog" <rick@kampsax.dtu.dk> wrote in message
> news:afk5cd$llr$1@eising.k-net.dk...
> > der er tale om (pointen er blot at man ønsker at gøre en given iterator
> > invalid)
[Jeg skrev:]
> Nej, ingen af delene vil virke med typiske STL-implementationer.
> At en iterator er ugyldig, er en tilstand der forventes at eksistere inde
i
> programmørens hoved, og et begreb der giver mening i

Ved at læse videre i tråden kan jeg se at vi vist mener forskellige til med
"invalid iterator".

I standardens ordlyd er der visse operationer på en containere, der kan få
alle eksisterende iteratorer ind i containeren til at blive ugyldige. Det er
fx push_back på en std::vector, og desværre mange andre ting. Iteratorerne
"ved det ikke selv", og der er ikke nogen måde at "aflæse" denne ugyldighed
på. Programmøren skal bare vide det. Det er "undefined behaviour" at gøre
noget som helst med en ugyldig iterator (udover at destruere den).

Det I taler om, fx MyVector.end(), er ikke ugyldige iteratorer.
MyVector.end() er en helt fin og gyldig iterator ind i en vektor. Man må
bare ikke "++" eller dereferere den. Men man kan jo fint sige "*(it-1)=42",
forudsat at vektoren har mindst ét element, hvilket viser at "it" er en
gyldig iterator.

Hvis jeg (nu) har forstået det rigtigt, så foreslå du at operationer der
returnerer "exceptionelle værdier" (fx std::find, hvis elementet ikke blev
fundet) alle benytter en fælles værdi, std::invalid_iterator.
Problemet med dette er at iteratorer fra forskellige containerer kan have
(og typisk vil have) forskellig type, så dette approach vil ikke virke.
Så skal man snarere, som Claus Rasmussen foreslår, have en speciel "No
value" iterator for hver klasse, snarere end for hvert objekt. std::string
klassen har SVJH dette, men jeg kan ikke huske hvad den hedder. Er det ikke
noget med at den har en std::string::no_pos, eller lignende, i stedet for
std::string::end()?


Jeg benytter enhver lejlighed til at reklamere for Python (og dynamiske
typer i almindelighed).
Dette problem virker meget, meget fjernt og latterligt for en
Python-programmør. Vil man endelig holde en "iterator eller ingenting" i en
variabel, så behøver man ikke at erklære dette som en seperat type, man
stopper bare "None" ind når man ikke har nogen (fornuftig) værdi.
Ganske vist skal man så igen "oppe i programmørens hoved" (som jeg brokkede
mig over før) vide at værdien *kan* være None, når man bruger den, men det
må man så dels dokumentere sig ud af, og dels får vi i det mindste noget
ordentlig diagnostics, hvis man forsøger at bruge en variabel som en
iterator når den er None. (I C++ crasher lortet bare).

Mvh. Bjarke





Thomas Krog (30-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 30-06-02 10:10

> I standardens ordlyd er der visse operationer på en containere, der kan få
> alle eksisterende iteratorer ind i containeren til at blive ugyldige. Det
er
> fx push_back på en std::vector, og desværre mange andre ting. Iteratorerne
> "ved det ikke selv", og der er ikke nogen måde at "aflæse" denne
ugyldighed
> på. Programmøren skal bare vide det. Det er "undefined behaviour" at gøre
> noget som helst med en ugyldig iterator (udover at destruere den).

ok, invalid_iterator er nok et dårligt navn. Måske ville std::null være
bedre.

> Hvis jeg (nu) har forstået det rigtigt, så foreslå du at operationer der
> returnerer "exceptionelle værdier" (fx std::find, hvis elementet ikke blev
> fundet) alle benytter en fælles værdi, std::invalid_iterator.
> Problemet med dette er at iteratorer fra forskellige containerer kan have
> (og typisk vil have) forskellig type, så dette approach vil ikke virke.
> Så skal man snarere, som Claus Rasmussen foreslår, have en speciel "No
> value" iterator for hver klasse, snarere end for hvert objekt. std::string
> klassen har SVJH dette, men jeg kan ikke huske hvad den hedder. Er det
ikke
> noget med at den har en std::string::no_pos, eller lignende, i stedet for
> std::string::end()?

Jeg mener nu godt man kan nøjes med en variabel der klarer det hele:

class Null{
template<class A>
bool operator==(const A& a)const{return a==*this;}
} null;

Så skal de forskellige typer af iteratorer hver have en assignment operator
der finder ud af hvordan de hver især kan tildeles null værdien:

template<class A>
class vector{
class iterator{
iterator& operator=(Null n){a= 0;}
bool operator==(const Null& i) const{return a==0;}
A* a;
};
};

Tilsvarende burde det være muligt at lave implementeringer af de øvrige
containere

> Jeg benytter enhver lejlighed til at reklamere for Python (og dynamiske
> typer i almindelighed).
> Dette problem virker meget, meget fjernt og latterligt for en
> Python-programmør. Vil man endelig holde en "iterator eller ingenting" i
en
> variabel, så behøver man ikke at erklære dette som en seperat type, man
> stopper bare "None" ind når man ikke har nogen (fornuftig) værdi.
> Ganske vist skal man så igen "oppe i programmørens hoved" (som jeg
brokkede
> mig over før) vide at værdien *kan* være None, når man bruger den, men det
> må man så dels dokumentere sig ud af, og dels får vi i det mindste noget
> ordentlig diagnostics, hvis man forsøger at bruge en variabel som en
> iterator når den er None. (I C++ crasher lortet bare).

Og det mener jeg også godt der kan lade sig gøre hvis man udvider stl
headerne i c++.



Claus Rasmussen (30-06-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 30-06-02 21:17

Thomas Krog wrote:

> Og det mener jeg også godt der kan lade sig gøre hvis man udvider stl
> headerne i c++.

Du tænker ikke langt nok. Den løsning du skitserer vil kun virke
i een situation: Når en iterator kører ud over enden af dens
container.

I alle andre situationer, hvor en iterator invalideres, vil din
løsning ikke virke - med mindre du accepterer et gigantisk overhead
for at spore alle iteratorer, der invalideres af en given operation.

(og du overser endda, at du skal have en test hver eneste gang du
ændrer en iterator for at checke om den er røget ud over enden).

-Claus


Thomas Krog (30-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 30-06-02 22:09

"Claus Rasmussen" <clr@cc-consult.dk> wrote in message
news:afnovp$dv4$1@sunsite.dk...
> Thomas Krog wrote:
>
> > Og det mener jeg også godt der kan lade sig gøre hvis man udvider stl
> > headerne i c++.
>
> Du tænker ikke langt nok. Den løsning du skitserer vil kun virke
> i een situation: Når en iterator kører ud over enden af dens
> container.
>
> I alle andre situationer, hvor en iterator invalideres, vil din
> løsning ikke virke - med mindre du accepterer et gigantisk overhead
> for at spore alle iteratorer, der invalideres af en given operation.
>
> (og du overser endda, at du skal have en test hver eneste gang du
> ændrer en iterator for at checke om den er røget ud over enden).
>
> -Claus

Jeg tror vi misforstår hinanden. Som jeg læser Bjarkes post er det eneste
han kræver at man kan tildele hvad som helst til none samt undgå en fejl når
man bruger noget der har værdien none. Det mener jeg også godt man kan i c++
(med den udvidelse jeg foreslår)



Claus Rasmussen (30-06-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 30-06-02 22:15

Thomas Krog wrote:

> Jeg tror vi misforstår hinanden. Som jeg læser Bjarkes post er det eneste
> han kræver at man kan tildele hvad som helst til none samt undgå en fejl
> når man bruger noget der har værdien none. Det mener jeg også godt man kan
> i c++ (med den udvidelse jeg foreslår)

Ok. Syntaktisk kan det godt lade sig gøre. Men de praktiske problemer
ved at gøre det er nok årsagen til, at det ikke er en del C++.

Du kunne i øvrigt tage et kig på Boost::Any, der bevæger sig lidt i
de samme baner: www.boost.org

-Claus


Thomas Krog (01-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 01-07-02 02:09

> > Jeg tror vi misforstår hinanden. Som jeg læser Bjarkes post er det
eneste
> > han kræver at man kan tildele hvad som helst til none samt undgå en fejl
> > når man bruger noget der har værdien none. Det mener jeg også godt man
kan
> > i c++ (med den udvidelse jeg foreslår)
>
> Ok. Syntaktisk kan det godt lade sig gøre. Men de praktiske problemer
> ved at gøre det er nok årsagen til, at det ikke er en del C++.

Vi misforstår vist stadig hinanden. Min kommentar gik kun på den del af
Bjarkes indlæg som jeg citerede. Det har derfor _ikke_ været min tanke at
c++ skulle udvides med en facilitet der kunne "reparerere alle iteratorer" /
"markere dem invalid" efter et kald af erase på en vector.

Det var nok også min formulering:
"undgå en fejl når man bruger noget der har værdien none"
der var dårlig. Jeg mente blot noget langt mere simpelt så som:
std::vector<int>::iterator i;
assert(i != std::null);
for at undgå en udefineret opførsel ved brug af i. Jeg overvejede ikke lige
at det kunne forståes anderledes.



Claus Rasmussen (02-07-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 02-07-02 07:49

Thomas Krog wrote:

> Vi misforstår vist stadig hinanden. Min kommentar gik kun på den del af
> Bjarkes indlæg som jeg citerede. Det har derfor _ikke_ været min tanke at
> c++ skulle udvides med en facilitet der kunne "reparerere alle iteratorer"
> / "markere dem invalid" efter et kald af erase på en vector.

Jeg forstår så stadigvæk ikke, hvad du vil bruge din optimering til. I
praksis altså. Der er 117 andre muligheder for at spare den reference
til containere, som du vil fjerne med din null-iterator (som det nok er
bedre at kalde den for at undgå forvirring).

Jeg spekulerer lidt på, om du ikke er ved at bedrive objekt-orienteret
programmering på noget, som i bund og grund er et almindeligt proceduralt
algoritme problem ? Altså at du forsøger at indkapsle et algoritmisk greb
(at have en null-iterator) i et interface, som skjuler grebet.


> ... Jeg mente blot noget langt mere simpelt så som:
> std::vector<int>::iterator i;
> assert(i != std::null);

Ja. Og dermed vil du påføre alle andre, der /ikke/ har behov for null-
iteratoren en ekstra omkostning: Initialiseringen af 'i' til null-værdien.
Det er det, der ligger bag designet af STL: At hvis der er features, du
ikke anvender, skal du ikke betale for dem.

-Claus


Thomas Krog (03-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 03-07-02 12:45


"Claus Rasmussen" <clr@cc-consult.dk> wrote in message
news:afric1$m27$1@sunsite.dk...
> Thomas Krog wrote:
>
> > Vi misforstår vist stadig hinanden. Min kommentar gik kun på den del af
> > Bjarkes indlæg som jeg citerede. Det har derfor _ikke_ været min tanke
at
> > c++ skulle udvides med en facilitet der kunne "reparerere alle
iteratorer"
> > / "markere dem invalid" efter et kald af erase på en vector.
>
> Jeg forstår så stadigvæk ikke, hvad du vil bruge din optimering til. I
> praksis altså. Der er 117 andre muligheder for at spare den reference
> til containere, som du vil fjerne med din null-iterator (som det nok er
> bedre at kalde den for at undgå forvirring).

Det er jeg ikke skrap nok til at kunne overskue (altså hvorvidt man i alle
tilfælde kan spare den reference).

> Jeg spekulerer lidt på, om du ikke er ved at bedrive objekt-orienteret
> programmering på noget, som i bund og grund er et almindeligt proceduralt
> algoritme problem ? Altså at du forsøger at indkapsle et algoritmisk greb
> (at have en null-iterator) i et interface, som skjuler grebet.

Jeg forstår ikke helt hvad du mener.

> > ... Jeg mente blot noget langt mere simpelt så som:
> > std::vector<int>::iterator i;
> > assert(i != std::null);
>
> Ja. Og dermed vil du påføre alle andre, der /ikke/ har behov for null-
> iteratoren en ekstra omkostning: Initialiseringen af 'i' til null-værdien.
> Det er det, der ligger bag designet af STL: At hvis der er features, du
> ikke anvender, skal du ikke betale for dem.

Det er nok mig der er dårlig til at forklare. Det jeg mente var:

void enEllerAndenAlgoritme(std::vector<int>::iterator i){
assert(i != std::null);
// udfør algoritmen
}



Thomas Krog (03-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 03-07-02 13:25

> > Jeg spekulerer lidt på, om du ikke er ved at bedrive objekt-orienteret
> > programmering på noget, som i bund og grund er et almindeligt
proceduralt
> > algoritme problem ? Altså at du forsøger at indkapsle et algoritmisk
greb
> > (at have en null-iterator) i et interface, som skjuler grebet.
>
> Jeg forstår ikke helt hvad du mener.

(det er især ordet greb jeg ikke forstår)



Claus Rasmussen (04-07-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 04-07-02 11:12

Thomas Krog wrote:

>> Jeg spekulerer lidt på, om du ikke er ved at bedrive objekt-orienteret
>> programmering på noget, som i bund og grund er et almindeligt proceduralt
>> algoritme problem ? Altså at du forsøger at indkapsle et algoritmisk greb
>> (at have en null-iterator) i et interface, som skjuler grebet.
>
> Jeg forstår ikke helt hvad du mener.

At du gør dig en allerhelvedes masse anstrengelser for at undgå at løse
et problem med en simpel algoritme.

Jeg mangler stadigvæk svar på, hvorfor du ikke bare udnævner en bestemt
iterator til at være null-iteratoren og så initialiserer dit array med
den ?

[...]

> Det er nok mig der er dårlig til at forklare. Det jeg mente var:
>
> void enEllerAndenAlgoritme(std::vector<int>::iterator i){
> assert(i != std::null);
> // udfør algoritmen
> }

Ja, men hvornår bliver 'i' tildelt værdien 'null' ? Når den initiali-
seres ? I så fald indfører du en feature, der har omkostninger for alle,
men kun anvendelse for dig.

Og hvis du selv tildeler den værdien 'null', har jeg svært ved at se
fordelen frem for blot at tildele den en anden værdi, som du beslutter
dig for at kalde 'null'.

-Claus





Thomas Krog (04-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 04-07-02 18:52

> Jeg mangler stadigvæk svar på, hvorfor du ikke bare udnævner en bestemt
> iterator til at være null-iteratoren og så initialiserer dit array med
> den ?

Primært fordi det er besværligt at oprette en null iterator for hver eneste
type man bruger. Desuden er det lettere at læse andre folks kode hvis alle
bruger den samme slags null-iterator. (man allokerer jo heller ikke et
stykke hukommelse og opretter en null pointer udfra det!)

> Ja, men hvornår bliver 'i' tildelt værdien 'null' ? Når den initiali-
> seres ? I så fald indfører du en feature, der har omkostninger for alle,
> men kun anvendelse for dig.

Nej 'i' bliver kun tildelt værdien 'null' når brugeren eksplicit skriver
det.



Claus Rasmussen (29-06-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 29-06-02 17:53

Thomas Krog wrote:

> Hvorfor kan man ikke gøre en iterator invalid på en sådan måde at man
> senere kan undersøge om den er invalid uden at være i besiddelse af
> containeren?

Øhh... hvad vil du bruge den til ?

Der er nemlig flere løsninger, men de afhænger alle af, hvilket problem,
du har.

F.eks er container.end() i mange sammenhænge en fin invalid iterator.
I andre sammenhænge laver du sådan noget:

const vector<int> v(0);
const vector<int>::iterator invalid_iterator = v.end();

Som så er en konstant iterator, du kan bruge i alle sammenhænge, hvor
du ønsker en iterator, der med garanti ikke er valid.

-Claus


Thomas Krog (29-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 29-06-02 21:14

> > Hvorfor kan man ikke gøre en iterator invalid på en sådan måde at man
> > senere kan undersøge om den er invalid uden at være i besiddelse af
> > containeren?
>
> Øhh... hvad vil du bruge den til ?

Undgå at slæbe containeren med rundt. (især til små objekter hvor størrelsen
kan være væsentlig)

[snip]
> I andre sammenhænge laver du sådan noget:
>
> const vector<int> v(0);
> const vector<int>::iterator invalid_iterator = v.end();
>
> Som så er en konstant iterator, du kan bruge i alle sammenhænge, hvor
> du ønsker en iterator, der med garanti ikke er valid.

Hvordan kan man være sikker på der ikke opstår en anden gyldig iterator j så
j == v.end() ? Siger c++ standarden noget om det?



Claus Rasmussen (29-06-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 29-06-02 21:21

Thomas Krog wrote:

>> > Hvorfor kan man ikke gøre en iterator invalid på en sådan måde at man
>> > senere kan undersøge om den er invalid uden at være i besiddelse af
>> > containeren?
>>
>> Øhh... hvad vil du bruge den til ?
>
> Undgå at slæbe containeren med rundt. (især til små objekter hvor
> størrelsen kan være væsentlig)

Sådan lidt mere konkret ? Metoden man anvender afhænger som sagt af
det problem, man sidder med.

[...]

>> Som så er en konstant iterator, du kan bruge i alle sammenhænge, hvor
>> du ønsker en iterator, der med garanti ikke er valid.
>
> Hvordan kan man være sikker på der ikke opstår en anden gyldig iterator j
> så j == v.end() ? Siger c++ standarden noget om det?

Jeg er ikke sikker på, hvad standarden siger, men hvis du vil være
sikker på, at 'invalid_iterator' peger på noget andet end enhver
anden iterator, kan du bare ændre dens definition til 'v.begin()'.

Men jeg er ret sikker på, at du ikke får problemer alligevel.

-Claus


Thomas Krog (29-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 29-06-02 21:35


"Claus Rasmussen" <clr@cc-consult.dk> wrote in message
news:afl4qm$in1$1@sunsite.dk...
> Thomas Krog wrote:
>
> >> > Hvorfor kan man ikke gøre en iterator invalid på en sådan måde at man
> >> > senere kan undersøge om den er invalid uden at være i besiddelse af
> >> > containeren?
> >>
> >> Øhh... hvad vil du bruge den til ?
> >
> > Undgå at slæbe containeren med rundt. (især til små objekter hvor
> > størrelsen kan være væsentlig)
>
> Sådan lidt mere konkret ? Metoden man anvender afhænger som sagt af
> det problem, man sidder med.

Noget i stil med nedenstående (ikke compilet):

class Container{
public:
class Reference{
public:
operator bool() const{// her er problemet}
int& getValue(){return *i;}
private:
std::vector<int>::iterator i;
};

void erase(const Reference& r){v.erase(r.i);}
private:
std::vector<int> v;
};


>
> [...]
>
> >> Som så er en konstant iterator, du kan bruge i alle sammenhænge, hvor
> >> du ønsker en iterator, der med garanti ikke er valid.
> >
> > Hvordan kan man være sikker på der ikke opstår en anden gyldig iterator
j
> > så j == v.end() ? Siger c++ standarden noget om det?
>
> Jeg er ikke sikker på, hvad standarden siger, men hvis du vil være
> sikker på, at 'invalid_iterator' peger på noget andet end enhver
> anden iterator, kan du bare ændre dens definition til 'v.begin()'.

v.begin() == v.end()
så det hjælper næppe.

>
> Men jeg er ret sikker på, at du ikke får problemer alligevel.
>
> -Claus
>



Claus Rasmussen (29-06-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 29-06-02 22:05

Thomas Krog wrote:

> Noget i stil med nedenstående (ikke compilet):

Ok. Her er et forslag:

struct Container {
static const vector<int>::iterator invalid_iterator;

struct Reference {
operator bool() { return i != invalid_iterator; }
...
private:
vector<int>::iterator i;
};
...
};

Og så i .c filen:

static vector<int> dummy_vector(0);
const vector<int>::iterator Container::invalid_iterator =
dummy_vector.end();

Der er så lige et lille problem med rækkefølgen af initialiseringen
af de to statiske objekter, men det er en anden historie.


Men der er et mere generelt problem med dit eksempel: Din 'operator
bool' funktion opfatter jeg som en test for, om iteratoren er valid,
og det kan du kun fastslå ved også at have selve containeren. Uanset
om du har en generel invalid iterator.

F.eks:

typedef vector<int> Vect;
typedef Vect::iterator Iter;

vector<int> v(10);
vector<int>::iterator i = v.begin();
i += 9; // Peger på sidste element i containeren.
v.erase(end() - 1); // Sletter sidste element og invaliderer 'i'

I dette tilfælde (som er almindeligt forekommende) er 'i' blevet
invalideret uden at blive rørt ved. Så med mindre du vil holde
regnskab med, hvilke iteratorer, der refererer en given vektor
og tjekke dem alle for om de er blevet invalideret, når vektoren
ændres, er du out-of-luck.

Jeg vil råde dig til at se stort på de par clock-cykler de spenderer
på at kopiere en reference til containeren. Du kan sammenligne
med at en disk-access koster mindst 10.000 cykler.

-Claus


Thomas Krog (29-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 29-06-02 23:55

> Men der er et mere generelt problem med dit eksempel: Din 'operator
> bool' funktion opfatter jeg som en test for, om iteratoren er valid,
> og det kan du kun fastslå ved også at have selve containeren. Uanset
> om du har en generel invalid iterator.

Det problem kan løses ved at gøre constructoren til Reference klassen
private og gøre Reference friend med Container. Så kan Container sørge for
kun at oprette Reference objekter r hvorom der gælder at r.i != v.end().

> I dette tilfælde (som er almindeligt forekommende) er 'i' blevet
> invalideret uden at blive rørt ved. Så med mindre du vil holde
> regnskab med, hvilke iteratorer, der refererer en given vektor
> og tjekke dem alle for om de er blevet invalideret, når vektoren
> ændres, er du out-of-luck.

I mit tilfælde ved jeg at størrelsen af vektoren ikke bliver ændret.

> Jeg vil råde dig til at se stort på de par clock-cykler de spenderer
> på at kopiere en reference til containeren. Du kan sammenligne
> med at en disk-access koster mindst 10.000 cykler.

Det kommer meget an på situationen. Jeg laver ikke meget disk-access. På en
32 bit platform vil størrelsen af et Reference objektet normalt ændre sig
fra 4 byte til 8 byte (hvis man tilføjer en ekstra variabel). Har man derpå
en stor vektor med Reference objekter som der springes lidt rundt i kan det
give næsten dobbelt så mange cachemiss og dermed næsten den dobbelte
kørselstid (forudsat der ikke laves så mange andre beregninger).





Thomas Krog (30-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 30-06-02 00:34

> I mit tilfælde ved jeg at størrelsen af vektoren ikke bliver ændret.

Og det var tåbeligt af mig at medtage erase funktionen når jeg ved at der
ikke skal foretages sletninger indenfor det område som er væsentligt mht.
hastighed. Det må du undskylde.



Claus Rasmussen (30-06-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 30-06-02 00:41

Thomas Krog wrote:

>> I mit tilfælde ved jeg at størrelsen af vektoren ikke bliver ændret.
>
> Og det var tåbeligt af mig at medtage erase funktionen når jeg ved at der
> ikke skal foretages sletninger indenfor det område som er væsentligt mht.
> hastighed. Det må du undskylde.

Ok. Jeg vil ærligt talt foreslå dig at køre nogle tests på, hvad
det koster i tid, at slæbe containeren med rundt. Mit gæt er, at
du får svært ved at måle nogen forskel overhovedet.

Det er i hvert min egen dyrekøbte erfaring.

-Claus


Thomas Krog (30-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 30-06-02 11:40


> Ok. Jeg vil ærligt talt foreslå dig at køre nogle tests på, hvad
> det koster i tid, at slæbe containeren med rundt. Mit gæt er, at
> du får svært ved at måle nogen forskel overhovedet.

Jeg har hermed fulgt dit råd. Det viste sig at tage 68 % længere tid at
slæbe den reference med. Så min påstand om at der i nogle tilfælde næsten
var en faktor 2 til forskel var da ikke helt ved siden af. Her er resultatet
af kørslen under vc7 i release mode. (for en sikkerhedsskyld kører jeg lige
tiny testen både før og efter huge testen):

Benchmark of tiny Reference:
Sum: 2588890000
Time: 2994
Benchmark of huge Reference:
Sum: 3510578544
Time: 5037
Benchmark of tiny Reference:
Sum: 3510578544
Time: 2974

-----Kildekoden ser således ud------

#include <vector>
#include <iostream>
#include <time.h>

struct Container{
Container() : v(1000){
for(int i = 0; i != v.size(); ++i)
v[i] = clock();
}
std::vector<int> v;
};
struct ReferenceHuge{
public:
ReferenceHuge(Container& ic) : c(ic) {
static size_t c = 0;
c = clock() % ic.v.size();

i = ic.v.begin()+c;
}

std::vector<int>::iterator i;
Container& c;
};
struct ReferenceTiny{
public:
ReferenceTiny(Container& ic) {//: c(ic) {
static size_t c = 0;
c = clock() % ic.v.size();

i = ic.v.begin()+c;
}

std::vector<int>::iterator i;
//Container& c;
};

template<class R>
void benchmark(){
Container c;
typename std::vector<R> v(100000,R(c));
size_t sum = 0;
clock_t start = clock();
for(int k = 0; k != 100; ++k)
for(int i = 1; i != 100; ++i)
for(typename std::vector<R>::iterator j = v.begin(); j < v.end(); j += i)
sum += *(j->i);
double elapsed = 1000.0*(clock()-start);
std::cout << "Sum: " << sum << std::endl;
std::cout << "Time: " << elapsed/CLOCKS_PER_SEC << std::endl;
}

int main(){
std::cout << "Benchmark of tiny Reference:\n";
benchmark<ReferenceTiny>();
std::cout << "Benchmark of huge Reference:\n";
benchmark<ReferenceHuge>();
std::cout << "Benchmark of tiny Reference:\n";
benchmark<ReferenceTiny>();
return 0;
}



Claus Rasmussen (30-06-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 30-06-02 21:10

Thomas Krog wrote:

>> Ok. Jeg vil ærligt talt foreslå dig at køre nogle tests på, hvad
>> det koster i tid, at slæbe containeren med rundt. Mit gæt er, at
>> du får svært ved at måle nogen forskel overhovedet.
>
> Jeg har hermed fulgt dit råd. Det viste sig at tage 68 % længere tid at
> slæbe den reference med. Så min påstand om at der i nogle tilfælde næsten
> var en faktor 2 til forskel var da ikke helt ved siden af. ......

Du er ude i noget skummelt noget.

Jeg har kørt testen på min linux boks og får i første omgang samme
resultat som dig. Men efter lidt eksperimenteren har jeg fundet ud
af, at tidsforskellen alene skyldes forskellen i /størrelsen/ af de
to Reference klasser. Hvis jeg tilføjer en 'void* p' til TinyRefe-
rence uden at initialisere den og uden overhovedet at røre den senere
i koden, kører de to forskellige versioner lige hurtigt:

Direkte kopi af din test:

Benchmark of tiny Reference:
Time: 1330
Benchmark of huge Reference:
Time: 2190
Benchmark of tiny Reference:
Time: 1210

Test hvor der er tilføjet en dummy 'void* p' til TinyReference:

Benchmark of tiny Reference:
Time: 2260
Benchmark of huge Reference:
Time: 2250
Benchmark of tiny Reference:
Time: 2250

Forskellen er helt væk i den version. Desuden bemærker man at
kørselstiden for begge typer referencer er vokset en smule men
det drejer sig om mindre end 3%.

Så det er alene cache hits du optimerer. Men det giver en markant
større forskel end jeg regnede med, må jeg indrømme.

Jeg vil dog stadig tage forbehold for om effekten er målelig i dit
endelige program. Du skal ikke tilføje med "kød" på dit test-skelet
før du alligevel smadrer cachen.

F.eks prøvede jeg at tilføje et kald til flg. funktion i dit inderste
loop for at fylde cachen:

int spam() {
vector<int> v(1000);
int sum = 0;

for (int i = 0; i < 1000; ++i)
sum += v[i];

return sum;
}

Det giver følgende benchmarks:

Benchmark of tiny Reference:
Time: 38140
Benchmark of huge Reference:
Time: 38260
Benchmark of tiny Reference:
Time: 38150

Dvs. forskellen er væk igen.


Men mht. dit konkrete problem, kunne du udnytte, at vektorer kan
indexeres med integers. Og så kan du bruge -1 som markør for en
invalid iterator.

-Claus


Thomas Krog (30-06-2002)
Kommentar
Fra : Thomas Krog


Dato : 30-06-02 22:19

> Jeg har kørt testen på min linux boks og får i første omgang samme
> resultat som dig. Men efter lidt eksperimenteren har jeg fundet ud
> af, at tidsforskellen alene skyldes forskellen i /størrelsen/ af de
> to Reference klasser.

Jep og det var lige præcis det der oprindeligt var min pointe med den
hastighedstest. Citat fra noget jeg skrev tidligere i tråden:
[citat start]
> Det kommer meget an på situationen. Jeg laver ikke meget disk-access. På
en
> 32 bit platform vil størrelsen af et Reference objektet normalt ændre sig
> fra 4 byte til 8 byte (hvis man tilføjer en ekstra variabel). Har man
derpå
> en stor vektor med Reference objekter som der springes lidt rundt i kan
det
> give næsten dobbelt så mange cachemiss og dermed næsten den dobbelte
> kørselstid (forudsat der ikke laves så mange andre beregninger).
[citat slut]

> F.eks prøvede jeg at tilføje et kald til flg. funktion i dit inderste
> loop for at fylde cachen:

Enig der skal ikke ændres meget før kørselstiderne er anderledes. Min pointe
var også bare at der findes situationer hvor der næsten er en faktor 2 til
forskel. (og det synes jeg ikke er betryggende når jeg skal konstruere en
generel datastruktur!)

> Men mht. dit konkrete problem, kunne du udnytte, at vektorer kan
> indexeres med integers. Og så kan du bruge -1 som markør for en
> invalid iterator.

Dog er size_t ikke garanteret negative værdier. Men man kunne nok sige noget
i stil med:
std::numeric_limits<size_t>::max()

Det kunne dog være bedst med et generalt princip der virker for alle slags
containerer



Thomas Krog (01-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 01-07-02 02:26

> >> Ok. Jeg vil ærligt talt foreslå dig at køre nogle tests på, hvad
> >> det koster i tid, at slæbe containeren med rundt. Mit gæt er, at
> >> du får svært ved at måle nogen forskel overhovedet.
> >
> > Jeg har hermed fulgt dit råd. Det viste sig at tage 68 % længere tid at
> > slæbe den reference med. Så min påstand om at der i nogle tilfælde
næsten
> > var en faktor 2 til forskel var da ikke helt ved siden af. ......
>
> Du er ude i noget skummelt noget.

Jeg mener nu ikke testen er skummel. En kørselstid er en kørselstid uanset
om den spildes med cachemiss eller bruges til beregninger.

>Hvis jeg tilføjer en 'void* p' til TinyRefe-
>rence uden at initialisere den og uden overhovedet at røre den senere
>i koden, kører de to forskellige versioner lige hurtigt:

I ReferenceHuge testen bliver der heller ikke rørt ved referencen c fra
ReferenceHuge i det tidsrum hvori tidstagningen forløber. Dermed er det vel
også forståeligt nok at de to tidstests bliver ens.



Claus Rasmussen (02-07-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 02-07-02 07:50

Thomas Krog wrote:

>> Du er ude i noget skummelt noget.
>
> Jeg mener nu ikke testen er skummel. En kørselstid er en kørselstid uanset
> om den spildes med cachemiss eller bruges til beregninger.

En optimering er EMM skummel, hvis den _alene_ baserer sig på den
hardwarekonfiguration maskinen har. Du risikerer, at lave en fin
optimering på Intel, der kører ad h...... til på Sparc.

Som min test viste, forsvinder fordelen ved dit design fuldstændigt,
hvis cachen bliver trashet, som det ofte vil ske i real-life, når
der er rigtigt kød på det inderste loop og ikke bare nogle dummy
beregninger, som her. For slet ikke at tale om, hvad der vil ske på
en travl maskine, hvor adskillige processer slås om cachen.

-Claus


Thomas Krog (02-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 02-07-02 12:11

> En optimering er EMM skummel, hvis den _alene_ baserer sig på den
> hardwarekonfiguration maskinen har. Du risikerer, at lave en fin
> optimering på Intel, der kører ad h...... til på Sparc.

Det gælder vel for alle forbedringer(*) (selv forbedringer hvor der er en
orden til forskel)

Man kan dog dele forbedringer om i 2 grupper:
-forbedringer som ikke kan forringe kørselstiden uanset platform (hertil
hører min forbedring)
-forbedringer til en specifik platform (som forringer kørselstiden på andre
platforme)

> Som min test viste, forsvinder fordelen ved dit design fuldstændigt,
> hvis cachen bliver trashet, som det ofte vil ske i real-life, når
> der er rigtigt kød på det inderste loop og ikke bare nogle dummy
> beregninger, som her.

Det var også ret drastisk at indsætte et ekstra loop i innerloopet (med 1000
iterationer).
De innerloops jeg har set er typisk under 10 instruktioner (men det er nok
forskelligt fra branche til branche)
Imperisk regl kaldet 80-20 reglen: 80 % af resourcerne bruges af 20 % af
koden.

> For slet ikke at tale om, hvad der vil ske på
> en travl maskine, hvor adskillige processer slås om cachen.

Enig - jeg tror dog fremtiden ligger i multi-cpu'er (og så vil man sjældent
have flere tråde kørende på en cpu)

(*) Jeg forsøger så vidt muligt at kalde det forbedringer (selv om jeg har
svært ved at vende mig til det), da optimering strengt taget er læren om at
finde optimum.



Claus Rasmussen (02-07-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 02-07-02 17:48

Thomas Krog wrote:

>> For slet ikke at tale om, hvad der vil ske på
>> en travl maskine, hvor adskillige processer slås om cachen.
>
> Enig - jeg tror dog fremtiden ligger i multi-cpu'er (og så vil man
> sjældent have flere tråde kørende på en cpu)

Jeg har på min i øjelibkket ikke særligt travle server 116 processer.
På min klientmaskine, som jeg sidder ved nu, har jeg 120 processer.

-Claus


Thomas Krog (02-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 02-07-02 18:34

> > Enig - jeg tror dog fremtiden ligger i multi-cpu'er (og så vil man
> > sjældent have flere tråde kørende på en cpu)
>
> Jeg har på min i øjelibkket ikke særligt travle server 116 processer.
> På min klientmaskine, som jeg sidder ved nu, har jeg 120 processer.

Ja, det er meget forskelligt hvordan computere bruges. Jeg har aldrig
skrevet noget kode hvor jeg har haft to beregningstunge tråde til at køre
samtidig på een cpu (desuden var det jo fremtiden (og multicpu'er) jeg skrev
om)



Claus Rasmussen (02-07-2002)
Kommentar
Fra : Claus Rasmussen


Dato : 02-07-02 18:44

Thomas Krog wrote:

> Ja, det er meget forskelligt hvordan computere bruges. Jeg har aldrig
> skrevet noget kode hvor jeg har haft to beregningstunge tråde til at køre
> samtidig på een cpu (desuden var det jo fremtiden (og multicpu'er) jeg
> skrev om)

Jo, men det handler jo ikke alene om antallet af tråde dit program
anvender. Cachen bruges af alle kørende programmer på maskinen og
med 100+ aktive processer skal du op i en maskine med et tilsvarende
antal CPUer, før du kan være sikker på, at dit program er alene om
cachen.

Hvis din ambition er at lave et generelt bibliotek, så dur' det altså
ikke at forudsætte, at dit program kører alene på maskinen.

-Claus


Thomas Krog (02-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 02-07-02 18:53

> Jo, men det handler jo ikke alene om antallet af tråde dit program
> anvender. Cachen bruges af alle kørende programmer på maskinen og
> med 100+ aktive processer skal du op i en maskine med et tilsvarende
> antal CPUer, før du kan være sikker på, at dit program er alene om
> cachen.

hvis det er en tråd som kun kører et par instruktioner en gang i sekundet er
det ikke meget den kan genere cachen.

> Hvis din ambition er at lave et generelt bibliotek, så dur' det altså
> ikke at forudsætte, at dit program kører alene på maskinen.

Det har jeg heller ikke tænkt mig at forudsætte. Jeg vil dog nødig lave et
bibliotek hvor det i visse situationer er muligt at lave et andet bibliotek
der er næsten dobbelt så hurtigt.



Jonas Meyer Rasmusse~ (02-07-2002)
Kommentar
Fra : Jonas Meyer Rasmusse~


Dato : 02-07-02 06:08

"Thomas Krog" <rick@kampsax.dtu.dk> wrote in message
news:afk385$h0u$1@eising.k-net.dk...
> Hvorfor kan man ikke gøre en iterator invalid på en sådan måde at man
senere
> kan undersøge om den er invalid uden at være i besiddelse af containeren?
>
> Dvs. noget i stil med:
> std::vector<int>::iterator i = std::invalid_iterator;
> if(i == std::invalid_iterator) ...;
>
> Så vidt jeg kan se burde det ikke give noget performance tab (fx. mht.
> størrelsen af en iterator)

Tænk på at en iterator er et generelt koncept, der skal fungere for mange
forskellige datastrukturer.
Det ville være simpelt at lave, hvis iterator-mønstret kun skulle arbejde på
vectorer..
Men du kan være rimeligt sikker på at de forskellige implementationer af
iteratorere i klasserne std::map,
std::list og std::vector er forskellige. Når de er forskellige, så betyder
det også at deres end() iterator, er forskellige..

Problemet kunne sikkert løses ved at benytte et arvehieraki med virtuelle
funktioner, men så _er_ der lige pludselig overhead,
og det vil vi jo ikke have


Hvis du stadig ikke er overbevist, så syntes jeg du skal overveje hvordan
iteratorer(og deres end() iterator)
skal implementeres.. Du vil formentlig finde ud af at du ikke kan lave en
std::invalid_iterator, der
fungerer for alle containertyperne...

Hvis du nu mener du har den magiske generelle løsning på problemet, så må du
jo endelig præsentere den
for os.

mvh Jonas




Thomas Krog (03-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 03-07-02 04:15

> Hvis du nu mener du har den magiske generelle løsning på problemet, så må
du
> jo endelig præsentere den
> for os.
>
> mvh Jonas

Jeg testede lige om det kunne lade sig gøre for vector og list containerne
og det ser ud til at virke ok. De er ikke fuldt funktionsdygtige (fx. kan
ingen af containerne kopieres og reserve kan kun kaldes een gang i vector
klassen). Dog kan jeg ikke se noget problem i at udvide princippet til at
virke for resten af vector, list og map. For alle implementeringer hvor en
iterator indeholder en pointer burde det være muligt at tildele denne
værdien null når iteratoren tildeles værdien estd::null.

#include <iostream>
#include <assert.h>

namespace estd{
struct Null{
template<class A>
bool operator==(const A& a)const{return a == *this;}
template<class A>
bool operator!=(const A& a)const{return a != *this;}
} null;

template<class V>
class list{
struct Element{
Element(Element* iPrev,Element* iNext) : prev(iPrev),next(iNext) {
int db = 0;
}
Element* prev;
Element* next;
};
struct ElementValue : public Element{
ElementValue(const V& iv,Element* iPrev,Element* iNext) :
Element(iPrev,iNext),v(iv) {
int db = 0;
}
V v;
};
public:
class iterator{
friend list<V>;
iterator(Element* ie) : e(ie) {}
public:
iterator(Null n) : e(0){}
V& operator*(){return static_cast<ElementValue*>(e)->v;}
iterator& operator++(){
e = e->next;
return *this;
}
iterator operator++(int){
iterator i = *this;
operator++();
return i;
}
iterator& operator--(){
e = e->prev;
return *this;
}
iterator& operator=(const estd::Null& n){
e = 0;
return *this;
}
bool operator==(iterator i) const {return e==i.e;}
bool operator!=(iterator i) const {return !operator==(i);}
private:
Element* e;
};

list() : elementEnd(&elementEnd,&elementEnd) {}
list(const list<V>& ls);
iterator begin(){
return iterator(elementEnd.next);
}
iterator end(){
return iterator(&elementEnd);
}
bool empty() const{return es.empty();}
template<class P>
void push_back(const P& p){
ElementValue* e = new ElementValue(V(p),elementEnd.prev,&elementEnd);
elementEnd.prev->next = e;
elementEnd.prev = e;
}
~list(){
for(iterator i = begin(); i != end();)
delete (i++).e;
}
private:

Element elementEnd;
};

template<class V>
class vector{
public:
class iterator{
friend vector<V>;
public:
iterator(V* iv) : v(iv) {}
iterator(Null n) : v(0) {}

iterator& operator=(const estd::Null& n){
v = 0;
return *this;
}

bool operator==(iterator i) const {return v==i.v;}
bool operator!=(iterator i) const {return !operator==(i);}
V& operator*(){return *v;}
V* operator->(){return v;}

iterator& operator++(){
++v;
return *this;
}
private:
V* v;
};
typedef V value_type;

vector() : bufferBegin(0),bufferEnd(0){
reserve(10);
}
vector(const vector<V>& v);
template<class A>
void push_back(const A& a){
assert(size() != capacity() && "the vector can not make an automatic
reserve");
new(bufferEnd)V(a);
++bufferEnd;
}
void reserve(size_t icap){
cap = icap;
size_t sz = size();
bufferBegin = (V*)malloc(cap*sizeof(V));
bufferEnd = bufferBegin+sz;
}
iterator begin(){return bufferBegin;}
iterator end(){return bufferEnd;}
size_t size() const{return bufferEnd-bufferBegin;}
size_t capacity() const{return cap;}
~vector(){
release();
}
private:
void release(){
for(iterator i = begin(); i != end(); ++i)
(*i).~V();
free(bufferBegin);
}
V* bufferBegin;
V* bufferEnd;
size_t cap;
};

}

int main(){
using std::cout;
using std::endl;

cout << "\n------- list test --------" << endl;
estd::list<int> test;
test.push_back(3);
test.push_back(4);
test.push_back(5);

for(estd::list<int>::iterator i = test.begin(); i != test.end(); ++i)
if(i != estd::null)
cout << "*i: " << *i << endl;

std::cout << "Reverse test: " << std::endl;
for(estd::list<int>::iterator i = test.end(); i != test.begin();)
if(i != estd::null){
--i;
cout << "*i: " << *i << endl;
}

estd::list<int>::iterator j = estd::null;
if(j == estd::null)
cout << "list iterator j == estd::null" << endl;
if(estd::null != test.end())
cout << "estd::null != test.end()" << endl;

cout << "\n------- vector test --------" << endl;

estd::vector<int> v;
v.push_back(3);
v.push_back(4);
v.push_back(5);

for(estd::vector<int>::iterator i = v.begin(); i != v.end(); ++i)
if(i != estd::null)
cout << "*i: " << *i << endl;

estd::vector<int>::iterator k = estd::null;
if(k == estd::null)
cout << "vector iterator k == estd::null" << endl;

if(estd::null != v.end())
cout << "estd::null != v.end()" << endl;

int wait;
std::cin >> wait;
return 0;
}



Thomas Krog (03-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 03-07-02 04:18

> Hvis du nu mener du har den magiske generelle løsning på problemet, så må
du
> jo endelig præsentere den
> for os.
>
> mvh Jonas

Jeg testede lige om det kunne lade sig gøre for vector og list containerne
og det ser ud til at virke ok. De er ikke fuldt funktionsdygtige (fx. kan
ingen af containerne kopieres og reserve kan kun kaldes een gang i vector
klassen). Dog kan jeg ikke se noget problem i at udvide princippet til at
virke for resten af vector, list og map. For alle implementeringer hvor en
iterator indeholder en pointer burde det være muligt at tildele denne
værdien null når iteratoren tildeles værdien estd::null.

#include <iostream>
#include <assert.h>

namespace estd{
struct Null{
template<class A>
bool operator==(const A& a)const{return a == *this;}
template<class A>
bool operator!=(const A& a)const{return a != *this;}
} null;

template<class V>
class list{
struct Element{
Element(Element* iPrev,Element* iNext) : prev(iPrev),next(iNext) {
int db = 0;
}
Element* prev;
Element* next;
};
struct ElementValue : public Element{
ElementValue(const V& iv,Element* iPrev,Element* iNext) :
Element(iPrev,iNext),v(iv) {
int db = 0;
}
V v;
};
public:
class iterator{
friend list<V>;
iterator(Element* ie) : e(ie) {}
public:
iterator(Null n) : e(0){}
V& operator*(){return static_cast<ElementValue*>(e)->v;}
iterator& operator++(){
e = e->next;
return *this;
}
iterator operator++(int){
iterator i = *this;
operator++();
return i;
}
iterator& operator--(){
e = e->prev;
return *this;
}
iterator& operator=(const estd::Null& n){
e = 0;
return *this;
}
bool operator==(iterator i) const {return e==i.e;}
bool operator!=(iterator i) const {return !operator==(i);}
private:
Element* e;
};

list() : elementEnd(&elementEnd,&elementEnd) {}
list(const list<V>& ls);
iterator begin(){
return iterator(elementEnd.next);
}
iterator end(){
return iterator(&elementEnd);
}
bool empty() const{return es.empty();}
template<class P>
void push_back(const P& p){
ElementValue* e = new ElementValue(V(p),elementEnd.prev,&elementEnd);
elementEnd.prev->next = e;
elementEnd.prev = e;
}
~list(){
for(iterator i = begin(); i != end();)
delete (i++).e;
}
private:

Element elementEnd;
};

template<class V>
class vector{
public:
class iterator{
friend vector<V>;
public:
iterator(V* iv) : v(iv) {}
iterator(Null n) : v(0) {}

iterator& operator=(const estd::Null& n){
v = 0;
return *this;
}

bool operator==(iterator i) const {return v==i.v;}
bool operator!=(iterator i) const {return !operator==(i);}
V& operator*(){return *v;}
V* operator->(){return v;}

iterator& operator++(){
++v;
return *this;
}
private:
V* v;
};
typedef V value_type;

vector() : bufferBegin(0),bufferEnd(0){
reserve(10);
}
vector(const vector<V>& v);
template<class A>
void push_back(const A& a){
assert(size() != capacity() && "the vector can not make an automatic
reserve");
new(bufferEnd)V(a);
++bufferEnd;
}
void reserve(size_t icap){
cap = icap;
size_t sz = size();
bufferBegin = (V*)malloc(cap*sizeof(V));
bufferEnd = bufferBegin+sz;
}
iterator begin(){return bufferBegin;}
iterator end(){return bufferEnd;}
size_t size() const{return bufferEnd-bufferBegin;}
size_t capacity() const{return cap;}
~vector(){
release();
}
private:
void release(){
for(iterator i = begin(); i != end(); ++i)
(*i).~V();
free(bufferBegin);
}
V* bufferBegin;
V* bufferEnd;
size_t cap;
};

}

int main(){
using std::cout;
using std::endl;

cout << "\n------- list test --------" << endl;
estd::list<int> test;
test.push_back(3);
test.push_back(4);
test.push_back(5);

for(estd::list<int>::iterator i = test.begin(); i != test.end(); ++i)
if(i != estd::null)
cout << "*i: " << *i << endl;

std::cout << "Reverse test: " << std::endl;
for(estd::list<int>::iterator i = test.end(); i != test.begin();)
if(i != estd::null){
--i;
cout << "*i: " << *i << endl;
}

estd::list<int>::iterator j = estd::null;
if(j == estd::null)
cout << "list iterator j == estd::null" << endl;
if(estd::null != test.end())
cout << "estd::null != test.end()" << endl;

cout << "\n------- vector test --------" << endl;

estd::vector<int> v;
v.push_back(3);
v.push_back(4);
v.push_back(5);

for(estd::vector<int>::iterator i = v.begin(); i != v.end(); ++i)
if(i != estd::null)
cout << "*i: " << *i << endl;

estd::vector<int>::iterator k = estd::null;
if(k == estd::null)
cout << "vector iterator k == estd::null" << endl;

if(estd::null != v.end())
cout << "estd::null != v.end()" << endl;

return 0;
}





Jonas Meyer Rasmusse~ (03-07-2002)
Kommentar
Fra : Jonas Meyer Rasmusse~


Dato : 03-07-02 10:57

Hej Igen.

Efter at have læst din kode, kan jeg ikke helt forstå hvad pointen er?

Af koden, er det eneste der ikke ligger i std containerne, Null klassen,
og en constructor der tager Null, og en assignment operator.

Min oprindelige opfattelse at dit spørgsmål, var at du ville have en generel
repræsentation af end() iteratoren, og det er åbenbart ikke det du har ment.

Problemet med løsningen, syntes jeg, er at det giver indtryk af at
man rent faktisk _har_ en generisk end() iterator. Da man ikke har det,
åbner op for endnu flere fælder for dem som ikke er klar over det.

Jonas



Thomas Krog (03-07-2002)
Kommentar
Fra : Thomas Krog


Dato : 03-07-02 12:22


"Jonas Meyer Rasmussen" <meyer@remove.diku.this.dk> wrote in message
news:afuhoi$8h8$1@eising.k-net.dk...
> Hej Igen.
>
> Efter at have læst din kode, kan jeg ikke helt forstå hvad pointen er?

At man kan tildele en iterator værdien null i stil med den måde som man
tildeler en pointer værdien 0. (uafhængigt af containeren)

> Af koden, er det eneste der ikke ligger i std containerne, Null klassen,
> og en constructor der tager Null, og en assignment operator.
>
> Min oprindelige opfattelse at dit spørgsmål, var at du ville have en
generel
> repræsentation af end() iteratoren, og det er åbenbart ikke det du har
ment.

Nej - så ambitiøs var min målsætning dog ikke :)

> Problemet med løsningen, syntes jeg, er at det giver indtryk af at
> man rent faktisk _har_ en generisk end() iterator. Da man ikke har det,
> åbner op for endnu flere fælder for dem som ikke er klar over det.
>
> Jonas

jo - jeg kan godt se at hvis folk begynder at skrive noget i stil med

for(estd::vector<int>::iterator i = v.begin(); i != estd::null; ++i){...}

så er der problemer.



Søg
Reklame
Statistik
Spørgsmål : 177496
Tips : 31968
Nyheder : 719565
Indlæg : 6408491
Brugere : 218887

Månedens bedste
Årets bedste
Sidste års bedste