Forum: PC-Programmierung c++ std::vector at(i) or [i]


von stdvector (Gast)


Lesenswert?

Ich habe einen Vector:
1
 
2
std::vector<int> test;
3
test.push_back(1);
4
test.push_back(2);
und nun frage ich mich was ist der Unterschied zwischen diesen beiden 
Formen:
1
std::cout << test[0] << test.at(0);

von Heiner (Gast)


Lesenswert?

vector.at wirft eine Exception, wenn ein unzulässiger Index übergeben 
wird und der []-Operator tut das nicht.

von stdvector (Gast)


Lesenswert?

Perfekt, danke!

von Klaus (Gast)


Lesenswert?

stdvector schrieb:
> Perfekt, danke

Das perfekt ist anders gemeint, aber das ist wirklich
das perfekte Konstrukt sich ins Knie zu schiessen.

Ich nutzte C++ wirklich gerne,
aber warum man nicht den default Weg abgesichert hat ist mir ein Rätsel.

Den wirklich jeder Anfänger rennt irgendwann in diese Falle.

von Tom K. (ez81)


Lesenswert?

Klaus schrieb:
> warum man nicht den default Weg abgesichert hat

Weil dann die alten C-Profi-Helden über die Kindergartensprache geheult 
hätten, die mit ihren unnötigen Features die Performance ausbremst.

von Oliver S. (oliverso)


Lesenswert?

Ach je, in eine Falle rennt man als Anfänger vielleicht (ganz bestimmt) 
bei std::map, aber doch nicht beim vector.

at() ist mit bounds checking, operator[] ohne. Das gilt für alle 
Container, und auch für das einfache C-Array ( das hat halt kein at() ).

Foolproof ist keine Programmiersprache, man sollte immer wissen, was man 
tut.

Oliver

von Wilhelm M. (wimalopaan)


Lesenswert?

Klaus schrieb:
> Das perfekt ist anders gemeint, aber das ist wirklich
> das perfekte Konstrukt sich ins Knie zu schiessen.

Vielleicht weil Du in einer expliziten for-loop mit Indizierung 
iterierst ...?

von Sven B. (scummos)


Lesenswert?

Klaus schrieb:
> stdvector schrieb:
>> Perfekt, danke
>
> Das perfekt ist anders gemeint, aber das ist wirklich
> das perfekte Konstrukt sich ins Knie zu schiessen.
>
> Ich nutzte C++ wirklich gerne,
> aber warum man nicht den default Weg abgesichert hat ist mir ein Rätsel.
>
> Den wirklich jeder Anfänger rennt irgendwann in diese Falle.

C++ hat viele Probleme, aber das ist keins davon. Der Weg, den man 
intuitiv wählt, also operator[], ist der schnelle. Wenn man sich für die 
Out-of-Bounds-Bedingung interessiert, wird man als Anfänger ja kaum aus 
dem Kopf "catch (std::out_of_range)" hinschreiben, sondern einen Blick 
in die Doku werfen. Wo das dann alles steht.

Beitrag #6449309 wurde von einem Moderator gelöscht.
von mh (Gast)


Lesenswert?

Heiner schrieb:
> vector.at wirft eine Exception, wenn ein unzulässiger Index übergeben
> wird und der []-Operator tut das nicht.

Auch operator[] darf eine Exception werfen, wenn ein Out-of-Bounds 
Zugriff erfolgt.

von Klaus (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Vielleicht weil Du in einer expliziten for-loop mit Indizierung
> iterierst ...?

Nein, einfach weil ich ein Freund defensiver Vorgehensweisen bin.
Ich weiss ja, dass Security bei der Erstellung der STL nicht im 
Bewustsein war.
Aber umgekehrt, sprich at() ohne Prüfung, waere mir persönlich lieber 
gewesen.

Ich nutze vor Jahren einen C Compiler der Boundary check konnte.
Reihenweise ist Code des Teams in Ausnahmen gerannt.
Heutzutage mit TDD vermutlich weniger wichtig.

Ist aber nur meine persönliche Meinung. Ich kann damit Leben.

von Wilhelm M. (wimalopaan)


Lesenswert?

mh schrieb:
> Heiner schrieb:
>> vector.at wirft eine Exception, wenn ein unzulässiger Index übergeben
>> wird und der []-Operator tut das nicht.
>
> Auch operator[] darf eine Exception werfen, wenn ein Out-of-Bounds
> Zugriff erfolgt.

Auszug aus cppreference:

---
Returns a reference to the element at specified location pos. No bounds 
checking is performed.

Notes
Unlike std::map::operator[], this operator never inserts a new element 
into the container. Accessing a nonexistent element through this 
operator is undefined behavior.
---

Ich denke, das ist unmissverständlich.

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Ich denke, das ist unmissverständlich.

Ja, wobei das hier nicht so ganz stimmt:

> No bounds checking is performed.

Es ist der Standardlib nicht verboten, bounds checking zu machen. Man 
sollte als Programmierer nur nicht davon ausgehen, dass es gemacht wird.

> Accessing a nonexistent element through this operator is undefined behavior.

"undefined behavior" heißt, dass der C++-Standard keinerlei Vorgaben 
dazu macht, was in diesem Fall passiert. Wenn der Entwickler der 
Standardbibliothek sich dazu entscheidet, in dem Fall eine 
out-of-bounds-Exception zu werfen, verstößt er damit nicht gegen den 
Standard.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> Ich denke, das ist unmissverständlich.
>
> Ja, wobei das hier nicht so ganz stimmt:
>
>> No bounds checking is performed.
>
> Es ist der Standardlib nicht verboten, bounds checking zu machen. Man
> sollte als Programmierer nur nicht davon ausgehen, dass es gemacht wird.

Sehe ich anders: hier ist kein Freiraum für eine Implementierung.

>
>> Accessing a nonexistent element through this operator is undefined behavior.
>
> "undefined behavior" heißt, dass der C++-Standard keinerlei Vorgaben
> dazu macht, was in diesem Fall passiert.

Nein, das wäre implementation-defined.
Bei einem bounds-checking / throw wäre es kein undefined-bevaiour, 
sondern klar definiert, was passiert, allerdings außerhalb der 
Festlegungen des Standard.

von Oliver S. (oliverso)


Lesenswert?

Wilhelm M. schrieb:
> Nein, das wäre implementation-defined.
> Bei einem bounds-checking / throw wäre es kein undefined-bevaiour,

Je nun, diese Diskussion,was "undefined behaviour" ist, ist müßig. 
Natürlich darf die lib da eine Exception werfen, oder die Anwendung 
terminieren, oder "out of bounds" nach stderr schreiben, oder die 
Festplatte formatieren. Man darf sich nur nicht drauf verlassen...

libstdc++ bietet z.B debug-Versionen der Container, (-D_GLIBCXX_DEBUG), 
die dann operator[] bounds-cheking haben.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Sehe ich anders: hier ist kein Freiraum für eine Implementierung.

Genau das Gegenteil ist der Fall: Der Freiraum für die Implementierung 
ist unendlich groß. Genau das heißt "undefined behavior".
Aber gut: Wenn der Compiler keinen Freiraum hat, was ist denn deiner 
Meinung nach das Verhalten, das laut Standard passieren muss, wenn man 
einen out-of-Bounds-Zugriff auf einen vector per operator[] versucht?

>> "undefined behavior" heißt, dass der C++-Standard keinerlei Vorgaben
>> dazu macht, was in diesem Fall passiert.
>
> Nein, das wäre implementation-defined.

Implementation-defined heißt, dass für den Compiler ein Verhalten 
definiert und dokumentiert sein muss.

> Bei einem bounds-checking / throw wäre es kein undefined-bevaiour,

Doch, denn undefined behavior heißt, dass JEDES Verhalten korrekt ist, 
und das beinhaltet auch das Werfen einer Exception.

> sondern klar definiert, was passiert, allerdings außerhalb der
> Festlegungen des Standard.

"undefined" bezieht sich auf den Standard - das Verhalten ist nicht 
durch den Standard definiert. Der Compiler darf machen, was er will - 
auch ein Verhalten definieren. Er muss aber nicht. Der Compiler wird 
nicht dazu gezwungen, irgendetwas unvorhersehbares zu tun.

von mh (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Returns a reference to the element at specified location pos. No bounds
> checking is performed.

> Ich denke, das ist unmissverständlich.

cppref ist unmissverständlich, aber wo steht das im Standard? Ich kann 
dort nur finden, dass at() einen Check durchführt, nichts zu operator[].

Ich habe explizit geschrieben, dass eine Exception geworfen wird, nichts 
von einem Check. Da, wie Rolf M. geschrieben hat, das behavior undefined 
ist, ist das auch zulässig, falls der Standard den Check verbietet.

von Luther B. (luther-blissett)


Lesenswert?

Wilhelm M. schrieb:

> Nein, das wäre implementation-defined.
> Bei einem bounds-checking / throw wäre es kein undefined-bevaiour,
> sondern klar definiert, was passiert, allerdings außerhalb der
> Festlegungen des Standard.

"Implementation defined" ist im C++ Standard die Festlegung, dass die 
Implementation an der Stelle ein definiertes und dokumentiertes 
Verhalten haben muss. Dein Compilerhersteller würde also verpflichtet 
in die Doku zu schreiben, wass passiert, wenn ein out of bounds Zugriff 
passiert und das auch konsequent umzusetzen. "Undefined" schreibt das 
eben nicht vor, Compiler/Bibliothek dürfen machen, was sie wollen, inkl. 
ihr Verhalten festzulegen und zu dokumentieren.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wilhelm M. schrieb:
>> Sehe ich anders: hier ist kein Freiraum für eine Implementierung.
>
> Genau das Gegenteil ist der Fall: Der Freiraum für die Implementierung
> ist unendlich groß. Genau das heißt "undefined behavior".

Es steht dort: no bounds checking. Daher darf das dort auch nicht 
ausgeführt werden. Kein Freiraum für eine Implementierung.

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Implementation-defined heißt, dass für den Compiler ein Verhalten
> definiert und dokumentiert sein muss.

Genau.

>> Bei einem bounds-checking / throw wäre es kein undefined-bevaiour,
>
> Doch, denn undefined behavior heißt, dass JEDES Verhalten korrekt ist,
> und das beinhaltet auch das Werfen einer Exception.

Ok, überzeugt: wenn dort UB steht, dann darf der Hersteller das durch IB 
erstzen, muss aber nicht. Nur wenn dort IB steht, muss er ein IB 
beschreiben.

von Oliver S. (oliverso)


Lesenswert?

Allerdings könnte der Compiler einen Zugriff auf ein std::Array 
außerhalb dessen Größe schlicht wegoptimieren (und eventuell noch viel 
mehr, was halt bei UB alles so passieren kann), wenn er das erkennen 
kann. Ob er das kann, kann ich allerdings nicht sagen.
Da käme die lib gar nicht mehr dazu, die exception zu werfen.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Rolf M. schrieb:
>> Implementation-defined heißt, dass für den Compiler ein Verhalten
>> definiert und dokumentiert sein muss.
>
> Genau.
>
>>> Bei einem bounds-checking / throw wäre es kein undefined-bevaiour,
>>
>> Doch, denn undefined behavior heißt, dass JEDES Verhalten korrekt ist,
>> und das beinhaltet auch das Werfen einer Exception.
>
> Ok, überzeugt: wenn dort UB steht, dann darf der Hersteller das durch IB
> erstzen, muss aber nicht.

Noch nicht ganz. Die Bezeichnungen beziehen sich auf den Standard 
selbst, nicht auf den Compiler. Undefined behavior heißt, dass der 
Standard das Verhalten nicht definiert und keinerlei Vorgaben dazu 
macht, was passiert. Implementation-defined behavior heißt, dass der 
Standard von der Implementation verlangt, dass sie es definiert (und 
dokumentiert).
Wenn etwas UB ist, bleibt es also UB, auch wenn der Compiler das 
Verhalten definiert.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
>> Ok, überzeugt: wenn dort UB steht, dann darf der Hersteller das durch IB
>> erstzen, muss aber nicht.
>
> Noch nicht ganz. Die Bezeichnungen beziehen sich auf den Standard
> selbst, nicht auf den Compiler. Undefined behavior heißt, dass der
> Standard das Verhalten nicht definiert. Implementation-defined behavior
> heißt, dass der Standard von der Implementation verlangt, dass sie es
> definiert (und dokumentiert).
> Wenn etwas UB ist, bleibt es also UB, auch wenn der Compiler das
> Verhalten definiert.

Genau so war es gemeint. Denn wo der Standard von UB spricht, kann es im 
Sinne des Standards kein IB geben.

von Einer K. (Gast)


Lesenswert?

Manche Diskussionen verstehe ich nicht....

mh schrieb:
> Auch operator[] darf eine Exception werfen, wenn ein Out-of-Bounds
> Zugriff erfolgt.
Könnte, aber macht in diesem Zusammenhang keinen Sinn.


Klaus schrieb:
> Ich weiss ja, dass Security bei der Erstellung der STL nicht im
> Bewustsein war.
Naja...

Ist es nicht klar, was die vector Entwickler sich dabei gedacht haben?

Dann hier zum mitmeißeln:
Eins der oberen Entwicklungsziele war: Performance
Das lässt sich mit Bereichsprüfungen zu Laufzeit nicht realisieren und 
genau in einer solchen zeitkritischen Situation sollen diese Container 
weiter verwendbar sein.

Aus dem Grund wird eine schnelle und eine sichere (Zugriffs-) Variante 
angeboten.


Fakt:
Wenn gesichert ist, dass die Bereichsgrenzen nicht überschritten werden, 
besteht kein Grund für weitere Laufzeitprüfungen.

Klar kann man sich ins Knie schießen, wenn man das nicht weiß/beachtet.

von mh (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Ist es nicht klar, was die vector Entwickler sich dabei gedacht haben?

Dann denk mal ein paar Meter weiter. Was ist mit dem std::vector, der im 
Debug-Mode die Grenzen überprüfen soll und im Relese nicht. Was machst 
du dann? Schreibst du eine Fallunterscheidung in deine Anwendung ein, um 
im Debug-Mode at statt [] zu nutzen?

von Wilhelm M. (wimalopaan)


Lesenswert?

mh schrieb:
> Arduino Fanboy D. schrieb:
>> Ist es nicht klar, was die vector Entwickler sich dabei gedacht haben?
>
> Dann denk mal ein paar Meter weiter. Was ist mit dem std::vector, der im
> Debug-Mode die Grenzen überprüfen soll und im Relese nicht. Was machst
> du dann? Schreibst du eine Fallunterscheidung in deine Anwendung ein, um
> im Debug-Mode at statt [] zu nutzen?

Entweder Debug-Mode der libstdc++ oder die Container parametrierbar 
halten, und dann ggf. einen anderen oder einen Wrapper verwenden.

von Einer K. (Gast)


Lesenswert?

mh schrieb:
> Dann denk mal ein paar Meter weiter.

Ich habe dir gesagt, was sich die Entwickler dabei gedacht haben, und 
nicht was ich davon halte.

Aber wenn du es unbedingt wissen willst:
Ich stimme den Entwicklern zu!

Performance und Sicherheit schließen sich in dem Fall gegenseitig aus.

Vergleich:
Du kommst mir vor, wie jemand, welcher gleichzeitig durch 2 Türen, aus 
einem Auto aussteigen will, und weil er das nicht hin bekommt, über die 
überflüssige Tür schimpft. (und in der Folge, im Auto verhungert)
Siehe: Buridans Esel

von Ben Gunn (Gast)


Lesenswert?

Oliver S. schrieb:
> Allerdings könnte der Compiler einen Zugriff auf ein std::Array
> außerhalb dessen Größe schlicht wegoptimieren (und eventuell noch viel
> mehr, was halt bei UB alles so passieren kann), wenn er das erkennen
> kann. Ob er das kann, kann ich allerdings nicht sagen.
> Da käme die lib gar nicht mehr dazu, die exception zu werfen.

Schau dir mal std::get<N>(std::array) an. Dann kompiliert dein zur 
Kompilierzeit bekannter aber out-of-bounds-Zugriff erst gar nicht.

https://en.cppreference.com/w/cpp/container/array/get

von Wilhelm M. (wimalopaan)


Lesenswert?

Ben Gunn schrieb:
> Oliver S. schrieb:
>> Allerdings könnte der Compiler einen Zugriff auf ein std::Array
>> außerhalb dessen Größe schlicht wegoptimieren (und eventuell noch viel
>> mehr, was halt bei UB alles so passieren kann), wenn er das erkennen
>> kann. Ob er das kann, kann ich allerdings nicht sagen.
>> Da käme die lib gar nicht mehr dazu, die exception zu werfen.
>
> Schau dir mal std::get<N>(std::array) an. Dann kompiliert dein zur
> Kompilierzeit bekannter aber out-of-bounds-Zugriff erst gar nicht.
>
> https://en.cppreference.com/w/cpp/container/array/get

Das ist trivial, aber hier nicht gemeint: siehe Eingangspost.

von mh (Gast)


Lesenswert?

Wilhelm M. schrieb:
> mh schrieb:
>> Arduino Fanboy D. schrieb:
>>> Ist es nicht klar, was die vector Entwickler sich dabei gedacht haben?
>>
>> Dann denk mal ein paar Meter weiter. Was ist mit dem std::vector, der im
>> Debug-Mode die Grenzen überprüfen soll und im Relese nicht. Was machst
>> du dann? Schreibst du eine Fallunterscheidung in deine Anwendung ein, um
>> im Debug-Mode at statt [] zu nutzen?
>
> Entweder Debug-Mode der libstdc++ oder die Container parametrierbar
> halten, und dann ggf. einen anderen oder einen Wrapper verwenden.

Damit handelt man sich aber ne ganze Menge mehr Komplexität ein, als ein 
einfaches
1
#ifdef __CHECK_BOUNDS
2
  assert((0 <= index) && (index < size());
3
#endif
oder
1
#ifdef __CHECK_BOUNDS
2
  if((index < 0) || (size() <= index) {
3
    throw Whatever();
4
  }
5
#endif
im operator[]. Ich glaube libcxx macht sowas.

Arduino Fanboy D. schrieb:
> mh schrieb:
>> Dann denk mal ein paar Meter weiter.
>
> Ich habe dir gesagt, was sich die Entwickler dabei gedacht haben, und
> nicht was ich davon halte.
Wenn es das ist was sich die Entwickler dabei gedacht haben, warum wird 
der Check dann nicht explizit verboten im Standard und warum ist 
operator[] dann noexcept? Du musst das ja beantworten können, du weißt 
ja, was die Entwickler denken.

> Aber wenn du es unbedingt wissen willst:
> Ich stimme den Entwicklern zu!
>
> Performance und Sicherheit schließen sich in dem Fall gegenseitig aus.
Das kommt darauf an, wie du Performance und Sicherheit definierst. Bei 
mir schließt sich das hier nicht aus.

von Wilhelm M. (wimalopaan)


Lesenswert?

mh schrieb:
> Damit handelt man sich aber ne ganze Menge mehr Komplexität ein, als ein
> einfaches

Nein, null overhead. bzw. genausoviel Overhead (wie nötig) und in Deiner 
Lösung. Nur ohne Präprozessor und total flexibel.

von mh (Gast)


Lesenswert?

Wilhelm M. schrieb:
> Nein, null overhead. bzw. genausoviel Overhead (wie nötig) und in Deiner
> Lösung. Nur ohne Präprozessor und total flexibel.

Ich habe nicht Overhead, sondern Komplexität geschrieben. Das ist ein 
Unterschied.

Das ist der zweite Satz, der in libstdc++ Doku unter "Using the Debug 
Mode" steht.
1
Note that this flag changes the sizes and behavior of standard class templates such as std::vector, and therefore you can only link code compiled with debug mode and code compiled without debug mode if no instantiation of a container is passed between the two translation units.

von Einer K. (Gast)


Lesenswert?

mh schrieb:
> Wenn es das ist was sich die Entwickler dabei gedacht haben, warum wird
> der Check dann nicht explizit verboten im Standard und warum ist
> operator[] dann noexcept? Du musst das ja beantworten können, du weißt
> ja, was die Entwickler denken.
Das ist ein bisschen Problematisch zu beantworten, da die Argumentation 
längst nicht nur auf die std Container zutrifft.
Einerseits hat noexcept einen dokumentierenden Charakter und 
andererseits erlaubt es weiter gehende (automatische) Optimierungen.
Es ist also schon der Performance zuträglich, welches ja eins der Design 
Ziele ist.

mh schrieb:
>> Performance und Sicherheit schließen sich in dem Fall gegenseitig aus.
> Das kommt darauf an, wie du Performance und Sicherheit definierst. Bei
> mir schließt sich das hier nicht aus.
Interessant!
Da scheinst du recht eigenwillige Definitionen zu haben.
Aber ok... jedem sein Himmelreich.

von mh (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> mh schrieb:
>> Wenn es das ist was sich die Entwickler dabei gedacht haben, warum wird
>> der Check dann nicht explizit verboten im Standard und warum ist
>> operator[] dann noexcept? Du musst das ja beantworten können, du weißt
>> ja, was die Entwickler denken.
> Das ist ein bisschen Problematisch zu beantworten, da die Argumentation
> längst nicht nur auf die std Container zutrifft.

Ich formuliere es nochmal etwas genauer.

- Warum verbietet der Standard den Bound-Check für 
std::vector::operator[] weder explizit noch implizit?

- Warum verlangt der Standerd kein noexcept für std::vector::operator[]?

Arduino Fanboy D. schrieb:
> mh schrieb:
>>> Performance und Sicherheit schließen sich in dem Fall gegenseitig aus.
>> Das kommt darauf an, wie du Performance und Sicherheit definierst. Bei
>> mir schließt sich das hier nicht aus.
> Interessant!
> Da scheinst du recht eigenwillige Definitionen zu haben.
> Aber ok... jedem sein Himmelreich.

Was genau ist hier dein Problem? Ich bekomme die Sicherheit im 
Debug-Build wenn ich explizit danach frage und die Performance im 
Release, ohne etwas in meiner Anwendung zu ändern. Wieso schließt sich 
das aus?

von Einer K. (Gast)


Lesenswert?

mh schrieb:
> Wieso schließt sich das aus?
Weil du nicht aus 2 Türen gleichzeitig das Auto verlassen kannst.

Mit dem "Debuggen" betrittst du einen dritten Weg.
z.B. Machst mit der Flex ein Loch ins Dach.
Die "Lösung" ist für die fertige Anwendung irrelevant.
Betrifft also gar nicht die Entscheidung zwischen sicher und performant.

mh schrieb:
> - Warum verbietet der Standard den Bound-Check für
> std::vector::operator[] weder explizit noch implizit?
>
> - Warum verlangt der Standerd kein noexcept für std::vector::operator[]?

Der "Standard" verlangt eine einheitliche (minimal) Schnittstelle.
Dazu gehört die "no-throw" Garantie für std::vector::operator[].
Die konkrete Implementierung darf sich unterscheiden.
Solche Freiheiten finden sich noch an Hunderten weiteren Stellen so, 
oder so ähnlich.

Warum der Standard da kein noexcept erwartet, kann ich dir nicht sagen.
Allerdings leitet sich der Einsatz von noexcept für 
std::vector::operator[] eigentlich zwangsläufig aus der "no-throw" 
Garantie ab.
Vielleicht kommt das ja noch. C++11 ist ja noch nicht so ganz alt.

von Oliver S. (oliverso)


Lesenswert?

Arduino Fanboy D. schrieb:
> Dazu gehört die "no-throw" Garantie für std::vector::operator[].

Wer garantiert dir das?

Oliver

von Einer K. (Gast)


Lesenswert?

Oliver S. schrieb:
> Arduino Fanboy D. schrieb:
>> Dazu gehört die "no-throw" Garantie für std::vector::operator[].
> Wer garantiert dir das?

? seltsame Frage...

Der "Standard" fordert es ein.
Der Implementierer hat es umzusetzen.

von Oliver S. (oliverso)


Lesenswert?

Dann mal etwas deutlicher: wo im Standard steht die generelle 
nothrow-Garantie für den operator[]?

Ich zitiere mal das, was cplusplus.com dazu schreibt:
1
Exception safety
2
If the container size is greater than n, the function never throws exceptions (no-throw guarantee).
3
Otherwise, the behavior is undefined.

Die sind allerdings nicht die normative Referenz, daher hätte ich gerne 
dazu eine Link auf eine Originalquelle.

Oliver

: Bearbeitet durch User
von Al Fine (Gast)


Lesenswert?


von Ben Gunn (Gast)


Lesenswert?

Oliver S. schrieb:

> Die sind allerdings nicht die normative Referenz, daher hätte ich gerne
> dazu eine Link auf eine Originalquelle.

https://isocpp.org/files/papers/N4860.pdf

std::vector ab 22.3.11, PDF-Seite 852ff, Seitennummer 840ff
1
// element access
2
constexpr reference       operator[](size_type n);
3
constexpr const_reference operator[](size_type n) const;
4
constexpr const_reference at(size_type n) const;
5
constexpr reference       at(size_type n);
6
constexpr reference       front();
7
constexpr const_reference front() const;
8
constexpr reference       back();
9
constexpr const_reference back() const;

Da steht kein noexcept.

Bei 22.2.3 "Sequence containers", clause 15:

> The member function at() provides bounds-checked access to container elements. 
at() throws out_of_range if n >= a.size().

von Al Fine (Gast)


Lesenswert?


von Oliver S. (oliverso)


Lesenswert?

Ben Gunn schrieb:
> Da steht kein noexcept.

Da steht außer der Signatur überhaupt nichts zum operator[] drin. Das 
ist dann noch weniger, als ich vermutet habe. Es steht ansonsten nur 
drin, daß at() bounds checking durchführt.

Oliver

von Al Fine (Gast)


Lesenswert?

using V = std::vector<int>;
static_assert(noexcept(std::declval<V>()[0]));

von mh (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> mh schrieb:
>> Wieso schließt sich das aus?
> Weil du nicht aus 2 Türen gleichzeitig das Auto verlassen kannst.

Mach mit deinen Türen was du willst. Die Analogie ist zu dämlich, um 
darauf sinnvoll zu antworten.

Arduino Fanboy D. schrieb:
> Warum der Standard da kein noexcept erwartet, kann ich dir nicht sagen.
> Allerdings leitet sich der Einsatz von noexcept für
> std::vector::operator[] eigentlich zwangsläufig aus der "no-throw"
> Garantie ab.
> Vielleicht kommt das ja noch. C++11 ist ja noch nicht so ganz alt.

Wie schon mehrfach gefragt, wo steht im Standard etwas von dieser 
Garantie?

Was der Standard von std::vector::operator[] fordert steht in dieser 
Tabelle in der vorletzten Zeile.
http://eel.is/c++draft/tab:container.seq.opt

Ich zitiere nochmal aus deinem vorherigen Beitrag:
Arduino Fanboy D. schrieb:
> Ich habe dir gesagt, was sich die Entwickler dabei gedacht haben, und
> nicht was ich davon halte.

Wir haben also festgestellt, dass du keine Ahnung hast, was sich die 
Entwickler dabei gedacht haben. Du hast nichtmal einen Blick in den 
Standard (das Ergebnis ihre Gedanken) geworfen.

von Al Fine (Gast)


Lesenswert?

mh schrieb:
> Was der Standard von std::vector::operator[] fordert steht in dieser
> Tabelle in der vorletzten Zeile.
> http://eel.is/c++draft/tab:container.seq.opt

(a[i] <=> *(a.begin() + i)) ^ (noexcept(*(a.begin() + i)) == true)
=> noexcept(a[i]) == true

Kommt nicht ganz hin: 
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3279.pdf

"... left as a library vendor quality-of-implementation feature ..."
Schließlich müsste man bei Tests sonst einen Signal-Handler bemühen.

Ex falso quodlibet - das Programm in der Situation sowieso alles machen.

von Klaus (Gast)


Lesenswert?

Kaum ist man Mal ein paar Stunden weg...

Ihr bestätigt eigentlich nur meine Meinung, dass die Lösung so sehr 
fehleranfällig ist und selbst erfahrene Programmierer am Ende über IB / 
UB stolpern.

Und falls das falsch rüber kam:

Natürlich ist es sinnvoll dass es einen performanten schnellen Zugriff 
gibt.
Aber den hätte man über at() lösen können und den sicheren definierten 
Zugriff über [].

Hätte keinem Weh getan und einige C++ Programme währen robuster.

von Oliver S. (oliverso)


Lesenswert?

Al Fine schrieb:
> using V = std::vector<int>;
> static_assert(noexcept(std::declval<V>()[0]));

Die gcc libc++ implementiert das so, aber das ist eben nur deren 
Interpretation des Standards.
1
operator[](size_type __n) _GLIBCXX_NOEXCEPT
2
{
3
   __glibcxx_requires_subscript(__n);
4
   return *(this->_M_impl._M_start + __n);
5
}

Oliver

von Al Fine (Gast)


Lesenswert?

Klaus schrieb:
> Aber den hätte man über at() lösen können und den sicheren definierten
> Zugriff über [].
>
> Hätte keinem Weh getan und einige C++ Programme währen robuster.

Aber warum sollte man es noch schwieriger und mühsamer machen, 
performanten Code zu schreiben? Die bei der Performance gesparte Mühe 
kann man doch besser da in die Tipparbeit und die nötigen extra 
Fallunterscheidungen bei templatisiertem Zugriff über [] investieren.

von Klaus (Gast)


Lesenswert?

Al Fine schrieb:
> Aber warum sollte man es noch schwieriger und mühsamer machen,
> performanten Code zu schreiben?

Ist es doch gar nicht.
At() und [] unterscheiden sich in 2 Buchstaben...

Man muss bei der Diskussion dran denken, das die Standardisierung zu 
anderen Zeiten war. Heute sind Compiler deutlich mächtiger und könnten 
einiges optimieren.

Viele Codierstandards fordert Defensive Programmierung. Das ist wichtig.

Was bringt mir ein Programm das performant ist und beim ersten Zugriff 
undefiniert aussteigt?

Schlechte Performance sieht man sofort und kam dann optimieren.
Schlechte Robustheit passiert erst im Ernstfall.
Welches ist der kritische Fall...?

von Al Fine (Gast)


Lesenswert?

Klaus schrieb:
> At() und [] unterscheiden sich in 2 Buchstaben...

Na, wenn das für dich der Unterschied ist, kannst du ja at() verwenden.
No problem at all.

von Klaus (Gast)


Lesenswert?

Mach ich. Ich hab's ja verstanden und bei mir geht Sicherheit vor 
Performance.

Ich hatte nur das meiner Meinung nach schlechte Design "Performances vor 
Sicherheit" kritisiert. Iss so, ich lebe damit. Aber es fällt mir bei 
Code Reviews immer wieder auf.


Warum meckern viele über strcpy?
Ist doch performanter als strncpy...?
😀

von Al Fine (Gast)


Lesenswert?

Klaus schrieb:
> Warum meckern viele über strcpy?
> Ist doch performanter als strncpy...?

Ja - und warum sollte man keines von beidem verwenden?

von Al Fine (Gast)


Lesenswert?

Al Fine schrieb:
> Klaus schrieb:
>> Warum meckern viele über strcpy?
>> Ist doch performanter als strncpy...?
>
> Ja - und warum sollte man keines von beiden verwenden?

von Klaus (Gast)


Lesenswert?

siehst du, du bist in die Falle getappt:
man nimmt natürlich strlcpy.

Aber warum pochst du bei str copies auf Sicherheit und bei [] vector 
operator nicht? das ist nicht konsequent.

Daher meine Regel:
Immer defensiv, nur bei Performance Problemen darauf verzichten.

Wünsche gute Nacht.

von Al Fine (Gast)


Lesenswert?

Klaus schrieb:
> Aber warum pochst du bei str copies auf Sicherheit und bei [] vector
> operator nicht? das ist nicht konsequent.

Naja, wenn out-of-range behaviour das ist, was ein Argument für strncpy 
oder strcpy abgibt, macht vermutlich keine der beiden Funktionen genau 
das, was man eigentlich haben wollte.

von mh (Gast)


Lesenswert?

Al Fine schrieb:
> mh schrieb:
>> Was der Standard von std::vector::operator[] fordert steht in dieser
>> Tabelle in der vorletzten Zeile.
>> http://eel.is/c++draft/tab:container.seq.opt
>
> (a[i] <=> *(a.begin() + i)) ^ (noexcept(*(a.begin() + i)) == true)
> => noexcept(a[i]) == true
>
> Kommt nicht ganz hin:
> http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3279.pdf
>
> "... left as a library vendor quality-of-implementation feature ..."
> Schließlich müsste man bei Tests sonst einen Signal-Handler bemühen.
>
> Ex falso quodlibet - das Programm in der Situation sowieso alles machen.

Was genau willst du sagen? Wo im Standard steht die von dir hier 
genannte noexcept Bedingung? In dem von dir verlinkten Dokument steht es 
nicht drin und wäre auch sonst nicht relevant, da es nicht Teil des 
Standards ist.

von Al Fine (Gast)


Lesenswert?

mh schrieb:
> Wo im Standard steht die von dir hier
> genannte noexcept Bedingung?

Die ist erschlossen anhand der von dir verlinkten Semantik des "a[i]".
Wenn a[i] die semantische Bedeutung "*(a.begin() + i)" hat, was heisst 
das dann, wenn "a.begin()" noexcept ist? Oder meinst du eine mögliche 
exception ist keine semantische Abweichung?

von Jemand (Gast)


Lesenswert?

Al Fine schrieb:
> Aber warum sollte man es noch schwieriger und mühsamer machen,
> performanten Code zu schreiben?

Wozu müssen selten ausgeführte Pfade (ergo der meiste Code) auf 
Performance optimiert werden? Der Unterschied zur sicheren Variante ist 
meist völlig insignifikant, wenn überhaupt messbar (besonders auf 
superskalaren Prozessoren). Genau dort die Sicherheit in irgendeiner 
Form aufwändiger zu machen schlägt sich ausschließlich in 
Sicherheitslücken nieder.

Die Checks brauchst du natürlich nicht, weil dir nie Programmierfehler 
unterlaufen mit deinen goldenen Händchen, kennt man.

von Klaus (Gast)


Lesenswert?

Jemand schrieb:
> Wozu müssen selten ausgeführte Pfade (ergo der meiste Code) auf
> Performance optimiert werden?

Volle Zustimmung.

Genau dafür gibt es Performance Tools.
Optimiert wird wenn man weiss wo man am effektivsten optimieren kann.

von Al Fine (Gast)


Lesenswert?

Jemand schrieb:
> Wozu müssen selten ausgeführte Pfade (ergo der meiste Code) auf
> Performance optimiert werden?

Wieso selten ausgeführt? Ich mache genau dann einen Bounds-Check, wenn 
ich den machen will und auch nur dann. Das hat mit Programmierfehlern 
nichts zu tun. Die standartisierten Klassen sind so designed, dass sie 
für möglichst viele use-cases brauchbar sind. Irgendwie gehört es dann 
dazu, dass sich Raw-Pointer, arrays und vectors in templateisiertem Code 
ohne größere Probleme austauschen lassen.

Jemand schrieb:
> Die Checks brauchst du natürlich nicht, weil dir nie Programmierfehler
> unterlaufen mit deinen goldenen Händchen, kennt man.

Danke. Nächster.

von Rolf M. (rmagnus)


Lesenswert?

Al Fine schrieb:
> Die ist erschlossen anhand der von dir verlinkten Semantik des "a[i]".
> Wenn a[i] die semantische Bedeutung "*(a.begin() + i)" hat, was heisst
> das dann, wenn "a.begin()" noexcept ist?

Nichts, da weder der Operator+, noch der Operator* des Iterators 
noexcept sein muss, und die sind auch der entscheidende Teil für die 
out-of-bounds-Prüfung.
Es gibt überhaupt nur einen Fall, in dem das Ergebnis von begin() 
out-of-bounds ist, und das ist, wenn der Container leer ist. Da ist das 
aber kein Fehler, sondern so gedacht.

: Bearbeitet durch User
von Al Fine (Gast)


Lesenswert?

Rolf M. schrieb:
> Nichts, da weder der Operator+, noch der Operator* des Iterators
> noexcept sein muss, und die sind auch der entscheidende Teil für die
> out-of-bounds-Prüfung.

Ja, gut... wenn dein + operator exceptions wirft, tut er das halt...

Rolf M. schrieb:
> Es gibt überhaupt nur einen Fall, in dem das Ergebnis von begin()
> out-of-bounds ist, und das ist, wenn der Container leer ist. Da ist das
> aber kein Fehler, sondern so gedacht.

Falsch, begin() liefert einen iterator. Der ist immer gültig, aber nicht 
immer de-referenzierbar.

von Rolf M. (rmagnus)


Lesenswert?

Al Fine schrieb:
> Falsch,

Nein, richtig, nur von dir falsch interpretiert.

> begin() liefert einen iterator. Der ist immer gültig, aber nicht
> immer de-referenzierbar.

Richtig, und wenn er nicht dereferenzierbar ist, ist er das, weil er 
out-of-bounds ist (verweist auf eins nach dem letzten Element).

von Al Fine (Gast)


Lesenswert?

Rolf M. schrieb:
> Al Fine schrieb:
>> Falsch,
>
> Nein, richtig, nur von dir falsch interpretiert.
>
>> begin() liefert einen iterator. Der ist immer gültig, aber nicht
>> immer de-referenzierbar.
>
> Richtig, und wenn er nicht dereferenzierbar ist, ist er das, weil er
> out-of-bounds ist (verweist auf eins nach dem letzten Element).

Ja, aber man kann den iterator beliebig inkrementieren und 
dekrementieren. Solange man ihn nicht dereferenziert gibt es da kein 
Problem. Im speziellen sollte keine Exception bei einem Ausdruck
1
newIter = iter + X - Y;
fliegen. Auch dann nicht, wenn iter+X oob ist.

von Al Fine (Gast)


Lesenswert?

Aber wie gesagt: Wenn deine Iteratoren das tun, dann tun sie das halt. 
:)

von Al Fine (Gast)


Lesenswert?

Worum es die ganze Zeit geht: Es ist sinnlos, um ein a[i] ein try-catch 
zu bauen, [i]es sei denn[/i] man schreibt einen Test-Driver oder sowas.
Über kaputten Code zu philosophieren ist müßig.

von mh (Gast)


Lesenswert?

Al Fine schrieb:
> Ja, aber man kann den iterator beliebig inkrementieren und
> dekrementieren. Solange man ihn nicht dereferenziert gibt es da kein
> Problem. Im speziellen sollte keine Exception bei einem AusdrucknewIter
> = iter + X - Y;
> fliegen. Auch dann nicht, wenn iter+X oob ist.

Wo steht diese Anforderung im Standard? Für Pointer gilt das z.B. nicht.

von Al Fine (Gast)


Lesenswert?

mh schrieb:
> Al Fine schrieb:
>> Ja, aber man kann den iterator beliebig inkrementieren und
>> dekrementieren. Solange man ihn nicht dereferenziert gibt es da kein
>> Problem. Im speziellen sollte keine Exception bei einem AusdrucknewIter
>> = iter + X - Y;
>> fliegen. Auch dann nicht, wenn iter+X oob ist.
>
> Wo steht diese Anforderung im Standard? Für Pointer gilt das z.B. nicht.

Sollte - nicht muss. Werde glücklich mit deinen Iteratoren, die das 
machen.

von mh (Gast)


Lesenswert?

Al Fine schrieb:
> Sollte - nicht muss. Werde glücklich mit deinen Iteratoren, die das
> machen.

Das ist also deine persönliche Meinung?

von Al Fine (Gast)


Lesenswert?

Ja, da muss man nicht groß diskutieren, denke ich.

Wenn mit deinem Konzept schon ein range-for auseinanderfliegt - wozu der 
Worte. Mach du einfach...

von Al Fine (Gast)


Lesenswert?

Und such das mal schön selbst im Standard!

von Oliver S. (oliverso)


Lesenswert?

Al Fine schrieb:
> mh schrieb:
>> Wo im Standard steht die von dir hier
>> genannte noexcept Bedingung?
>
> Die ist erschlossen anhand der von dir verlinkten Semantik des "a[i]".
> Wenn a[i] die semantische Bedeutung "*(a.begin() + i)" hat, was heisst
> das dann, wenn "a.begin()" noexcept ist? Oder meinst du eine mögliche
> exception ist keine semantische Abweichung?

a.begin() kann sein, was es will. Das entscheidende ist die 
Dereferenzierung. Wenn a.begin() + i hinter das Ende des Containers 
zeigt, dereferenzierst du mit *(a.begin() + i) einen not dereferencable 
iterator, und das ist undefined behaviour.

Damit sind wir dann wieder ganz am Anfang dieses Threads, und können 
erneut diskutieren, ob UB auch nicht erwartete exceptions einschliesst.

Oliver

von mh (Gast)


Lesenswert?

Al Fine schrieb:
> Wenn mit deinem Konzept schon ein range-for auseinanderfliegt - wozu der
> Worte. Mach du einfach...

Warum fliegt welche range-for auseinander? Ein paar mehr erklärende 
Worte von dir wären schon hilfreich.

von Rolf M. (rmagnus)


Lesenswert?

mh schrieb:
> Al Fine schrieb:
>> Wenn mit deinem Konzept schon ein range-for auseinanderfliegt - wozu der
>> Worte. Mach du einfach...
>
> Warum fliegt welche range-for auseinander? Ein paar mehr erklärende
> Worte von dir wären schon hilfreich.

Das würde mich auch interessieren. Zeigerarithmetik darf man nur 
innerhalb eines Arrays oder auf eins nach dessen Ende machen. Darüber 
hinaus ist das verboten, und zwar auch schon ohne Dereferenzierung. Da 
ein Zeiger auch als Random-Access-Iterator genutzt werden kann, würde 
ich vermuten, das für diese die gleichen Anforderungen gelten.

von Al Fine (Gast)


Lesenswert?

Rolf M. schrieb:
> Zeigerarithmetik darf man nur
> innerhalb eines Arrays oder auf eins nach dessen Ende machen.

Und genau da knallt es schon.

von Al Fine (Gast)


Lesenswert?

Ich meine, es steht mh ja frei, das so zu machen.

Wie ich zitierte

Al Fine schrieb:
> "... left as a library vendor quality-of-implementation feature ..."

Wenn er die "Qualität: unbenutzbar" anstrebt, kann er das ja gerne 
machen. Kann std-konform sein....

von mh (Gast)


Lesenswert?

Al Fine schrieb:
> Ich meine, es steht mh ja frei, das so zu machen.
>
> Wie ich zitierte
>
> Al Fine schrieb:
>> "... left as a library vendor quality-of-implementation feature ..."
>
> Wenn er die "Qualität: unbenutzbar" anstrebt, kann er das ja gerne
> machen. Kann std-konform sein....

Kannst du mal etwas genauer erklären was du meinst? Du musst hier nicht 
zwingend unter 140 Zeichen bleiben. Was steht mir frei "das so zu 
machen"? Warum soll was ich mache, was auch immer du meinst, unbenutzbar 
sein?

Al Fine schrieb:
> Rolf M. schrieb:
>> Zeigerarithmetik darf man nur
>> innerhalb eines Arrays oder auf eins nach dessen Ende machen.
>
> Und genau da knallt es schon.

Wo knallt was?

von Al Fine (Gast)


Lesenswert?

Weißt du... mach doch einfach einen eigenen Thread auf mit der Frage.

von Rolf M. (rmagnus)


Lesenswert?

Al Fine schrieb:
> Weißt du... mach doch einfach einen eigenen Thread auf mit der Frage.

Ok, du hast also einfach keinen Bock, dich verständlich auszudrücken. 
Warum soll er einen neuen Thread aufmachen, um dich zu fragen, wie du 
das meinst? Es bleibt weiterhin ein Rätsel, was das ominöse "das" sein 
soll, von dem du denkst, dass wir es tun und warum du glaubst, es müsse 
dann knallen.

von Al Fine (Gast)


Lesenswert?

Steht alles oben. Lesen, denken, verstehen. Wenn ihr schlicht zu dämlich 
seid, einen deutschen Text zu lesen, kann ich euch da sicher nicht 
helfen.

Sonst geht doch einfach mal den Thread zusammen durch und sucht den 
Punkt, wo ihr die Pronomen nicht mehr auflösen könnt. Das wäre dann 
schon fast so eine Art Selbsthilfegruppe.

von Wilhelm M. (wimalopaan)


Lesenswert?

Al Fine schrieb:
> Steht alles oben. Lesen, denken, verstehen. Wenn ihr schlicht zu dämlich
> seid, einen deutschen Text zu lesen, kann ich euch da sicher nicht
> helfen.
>
> Sonst geht doch einfach mal den Thread zusammen durch und sucht den
> Punkt, wo ihr die Pronomen nicht mehr auflösen könnt. Das wäre dann
> schon fast so eine Art Selbsthilfegruppe.

Wunderbar: endlich wird der Thread hier so, wie wir es gewohnt sind in 
dieser Gruppe ;-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.