(Ich suche ein oder zwei Beispiele, um den Punkt zu beweisen, keine Liste.)
War es jemals so, dass eine Änderung des C ++ - Standards (z. B. von 98 auf 11, 11 auf 14 usw.) das Verhalten des vorhandenen, wohlgeformten Benutzercodes mit definiertem Verhalten im Stillen veränderte? dh ohne Warnung oder Fehler beim Kompilieren mit der neueren Standardversion?
Anmerkungen:
- Ich frage nach standardbasiertem Verhalten, nicht nach der Auswahl der Autoren von Implementierern / Compilern.
- Je weniger der Code erfunden ist, desto besser (als Antwort auf diese Frage).
- Ich meine nicht Code mit Versionserkennung wie
#if __cplusplus >= 201103L
. - Antworten, die das Speichermodell betreffen, sind in Ordnung.
c++
language-lawyer
standardization
einpoklum
quelle
quelle
auto
. Vor C ++ 11 wurde einauto x = ...;
deklariertint
. Danach erklärt es, was auch immer...
ist.auto
Variablen vom Typ "wer" angegeben haben. Ich denke, Sie könnten wahrscheinlich einerseits die Anzahl der Menschen auf der Welt zählen, die diese Art von Code schreiben würden, mit Ausnahme der verschleierten C-Code-Wettbewerbe ...Antworten:
Der Rückgabetyp
string::data
ändert sich vonconst char*
zuchar*
in C ++ 17. Das könnte sicherlich einen Unterschied machenvoid func(char* data) { cout << data << " is not const\n"; } void func(const char* data) { cout << data << " is const\n"; } int main() { string s = "xyz"; func(s.data()); }
Ein bisschen erfunden, aber dieses legale Programm würde seine Ausgabe von C ++ 14 auf C ++ 17 ändern.
quelle
std::string
Änderungen für C ++ 17 gibt. Wenn überhaupt, hätte ich gedacht, dass die C ++ 11-Änderungen irgendwie zu einer stillen Verhaltensänderung geführt haben könnten. +1.Die Antwort auf diese Frage zeigt, wie das Initialisieren eines Vektors mit einem einzelnen
size_type
Wert zu einem unterschiedlichen Verhalten zwischen C ++ 03 und C ++ 11 führen kann.std::vector<Something> s(10);
C ++ 03 erstellt standardmäßig ein temporäres Objekt des Elementtyps
Something
und kopiert jedes Element im Vektor aus diesem temporären Objekt .C ++ 11 erstellt standardmäßig jedes Element im Vektor.
In vielen (den meisten?) Fällen führen diese zu einem äquivalenten Endzustand, aber es gibt keinen Grund dafür. Dies hängt von der Implementierung der
Something
Standard- / Kopierkonstruktoren ab.Siehe dieses erfundene Beispiel :
class Something { private: static int counter; public: Something() : v(counter++) { std::cout << "default " << v << '\n'; } Something(Something const & other) : v(counter++) { std::cout << "copy " << other.v << " to " << v << '\n'; } ~Something() { std::cout << "dtor " << v << '\n'; } private: int v; }; int Something::counter = 0;
C ++ 03 erstellt standardmäßig eine
Something
undv == 0
dann zehn weitere aus dieser. Am Ende enthält der Vektor zehn Objekte mit denv
Werten 1 bis einschließlich 10.C ++ 11 erstellt standardmäßig jedes Element. Es werden keine Kopien angefertigt. Am Ende enthält der Vektor zehn Objekte mit den
v
Werten 0 bis einschließlich 9.quelle
cv::mat
. Der Standardkonstruktor weist neuen Speicher zu, während der Kopierkonstruktor dem vorhandenen Speicher eine neue Ansicht erstellt.Die Norm enthält eine Liste der wichtigsten Änderungen in Anhang C [diff] . Viele dieser Änderungen können zu stillen Verhaltensänderungen führen.
Ein Beispiel:
int f(const char*); // #1 int f(bool); // #2 int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2
quelle
bool
Version per se keine beabsichtigte Änderung war, sondern nur ein Nebeneffekt anderer Konvertierungsregeln. Die eigentliche Absicht wäre es, die Verwirrung zwischen den Zeichenkodierungen zu stoppen. Die eigentliche Änderung besteht darin, dassu8
Literale früher gabenconst char*
, jetzt aber gebenconst char8_t*
.Jedes Mal, wenn sie der Standardbibliothek neue Methoden (und häufig Funktionen) hinzufügen, geschieht dies.
Angenommen, Sie haben einen Standardbibliothekstyp:
struct example { void do_stuff() const; };
ziemlich einfach. In einigen Standardrevisionen wird eine neue Methode oder Überladung oder neben irgendetwas hinzugefügt:
struct example { void do_stuff() const; void method(); // a new method };
Dies kann das Verhalten vorhandener C ++ - Programme stillschweigend ändern.
Dies liegt daran, dass die derzeit eingeschränkten Reflexionsfunktionen von C ++ ausreichen, um festzustellen, ob eine solche Methode vorhanden ist, und basierend darauf anderen Code auszuführen.
template<class T, class=void> struct detect_new_method : std::false_type {}; template<class T> struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};
Dies ist nur ein relativ einfacher Weg, um das Neue zu erkennen
method
. Es gibt unzählige Möglichkeiten.void task( std::false_type ) { std::cout << "old code"; }; void task( std::true_type ) { std::cout << "new code"; }; int main() { task( detect_new_method<example>{} ); }
Das gleiche kann passieren, wenn Sie Methoden aus Klassen entfernen.
Während dieses Beispiel direkt die Existenz einer Methode erkennt, kann diese Art von indirektem Geschehen weniger erfunden werden. Als konkretes Beispiel könnten Sie eine Serialisierungs-Engine haben, die entscheidet, ob etwas als Container serialisiert werden kann, basierend darauf, ob es iterierbar ist, oder ob es Daten enthält, die auf Rohbytes zeigen, und ein Größenelement, wobei eines bevorzugt wird das andere.
Der Standard fügt einem
.data()
Container eine Methode hinzu , und plötzlich ändert der Typ, welcher Pfad für die Serialisierung verwendet wird.Alles, was der C ++ - Standard tun kann, wenn er nicht einfrieren möchte, ist, die Art von Code, der lautlos bricht, selten oder irgendwie unvernünftig zu machen.
quelle
Oh Mann ... Der bereitgestellte Link cpplearner ist beängstigend .
Unter anderem hat C ++ 20 die C-artige Strukturdeklaration von C ++ - Strukturen nicht zugelassen.
typedef struct { void member_foo(); // Ill-formed since C++20 } m_struct;
Wenn Ihnen beigebracht wurde, solche Strukturen zu schreiben (und Leute, die "C mit Klassen" unterrichten, unterrichten genau das), sind Sie fertig .
quelle
typedef
meine Strukturen und ich werde mit Sicherheit meine Kreide nicht damit verschwenden. Dies ist definitiv eine Frage des Geschmacks, und während es sehr einflussreiche Leute (Torvalds ...) gibt, die Ihre Sichtweise teilen, werden andere Leute wie ich darauf hinweisen, dass eine Namenskonvention für Typen alles ist, was benötigt wird. Das Überladen des Codes mitstruct
Schlüsselwörtern trägt wenig zum Verständnis bei, dass ein Großbuchstabe (MyClass* object = myClass_create();
) nicht vermittelt. Ich respektiere es, wenn Sie dasstruct
in Ihrem Code wollen. Aber ich will es nicht in meinem.struct
nur für einfache alte Datentypen undclass
alles, was Mitgliedsfunktionen hat, zu verwenden. Aber Sie können diese Konvention in C nicht verwenden, da es in C keine gibtclass
.struct
tatsächlich POD ist. So wie ich C-Code schreibe, werden die meisten Strukturen nur durch Code in einer einzelnen Datei und durch Funktionen berührt, die den Namen ihrer Klasse tragen. Es ist im Grunde OOP ohne den syntaktischen Zucker. Auf diese Weise kann ich tatsächlich steuern, welche Änderungen innerhalb einesstruct
und welche Invarianten zwischen seinen Mitgliedern garantiert sind. Daherstructs
tendiere ich dazu, Mitgliedsfunktionen, private Implementierung, Invarianten und Abstracts von ihren Datenmitgliedern zu haben. Klingt nicht nach POD, oder?extern "C"
Blöcken verboten sind, sehe ich kein Problem mit dieser Änderung. Niemand sollte Strukturen in C ++ typisieren. Dies ist keine größere Hürde als die Tatsache, dass C ++ eine andere Semantik als Java hat. Wenn Sie eine neue Programmiersprache lernen, müssen Sie möglicherweise einige neue Gewohnheiten lernen.Hier ist ein Beispiel, das 3 in C ++ 03, aber 0 in C ++ 11 druckt:
template<int I> struct X { static int const c = 2; }; template<> struct X<0> { typedef int c; }; template<class T> struct Y { static int const c = 3; }; static int const c = 4; int main() { std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; }
Diese Verhaltensänderung wurde durch spezielle Behandlung für verursacht
>>
. Vor C ++ 11>>
war immer der richtige Schichtoperator.>>
Kann mit C ++ 11 auch Teil einer Vorlagendeklaration sein.quelle
>>
dieser Methode zunächst "informell mehrdeutig" .Trigraphen fielen
Quelldateien werden in einem physischen Zeichensatz codiert, der implementierungsdefiniert dem im Standard definierten Quellzeichensatz zugeordnet wird. Um Zuordnungen von einigen physischen Zeichensätzen zu berücksichtigen, die nicht die gesamte vom Quellzeichensatz benötigte Interpunktion hatten, definierte die Sprache Trigraphen - Sequenzen von drei gemeinsamen Zeichen, die anstelle eines weniger gebräuchlichen Interpunktionszeichens verwendet werden könnten. Der Präprozessor und der Compiler mussten diese verarbeiten.
In C ++ 17 wurden Trigraphen entfernt. Daher werden einige Quelldateien von neueren Compilern nur akzeptiert, wenn sie zuerst vom physischen Zeichensatz in einen anderen physischen Zeichensatz übersetzt werden, der dem Quellzeichensatz eins zu eins zugeordnet ist. (In der Praxis haben die meisten Compiler die Interpretation von Trigraphen nur optional gemacht.) Dies ist keine subtile Verhaltensänderung, sondern eine grundlegende Änderung, die verhindert, dass zuvor akzeptable Quelldateien ohne einen externen Übersetzungsprozess kompiliert werden.
Weitere Einschränkungen für
char
Der Standard bezieht sich auch auf den Ausführungszeichensatz , der implementierungsdefiniert ist, jedoch mindestens den gesamten Quellzeichensatz sowie eine kleine Anzahl von Steuercodes enthalten muss.
Der C ++ - Standard, der
char
als möglicherweise vorzeichenloser Integraltyp definiert ist und jeden Wert im Ausführungszeichensatz effizient darstellen kann. Mit der Darstellung eines Sprachrechtsanwalts können Sie argumentieren, dass achar
mindestens 8 Bit sein muss.Wenn Ihre Implementierung einen vorzeichenlosen Wert für verwendet
char
, wissen Sie, dass dieser zwischen 0 und 255 liegen kann und daher zum Speichern jedes möglichen Bytewerts geeignet ist.Wenn Ihre Implementierung jedoch einen signierten Wert verwendet, stehen Optionen zur Verfügung.
Die meisten würden das Zweierkomplement verwenden, was
char
einen Mindestbereich von -128 bis 127 ergibt. Das sind 256 eindeutige Werte.Eine andere Option war Vorzeichen + Größe, wobei ein Bit reserviert ist, um anzuzeigen, ob die Zahl negativ ist, und die anderen sieben Bits die Größe angeben. Dies würde
char
einen Bereich von -127 bis 127 ergeben, was nur 255 eindeutigen Werten entspricht. (Weil Sie eine nützliche Bitkombination verlieren, um -0 darzustellen.)Ich bin nicht sicher , dass der Ausschuß immer dies als Mangel ausdrücklich bezeichnet, aber es war , weil Sie nicht auf dem Standard verlassen konnten eine Hin- und Rückfahrt zu gewährleisten , von
unsigned char
zuchar
und würde wieder den ursprünglichen Wert erhalten. (In der Praxis haben alle Implementierungen dies getan, weil sie alle das Zweierkomplement für vorzeichenbehaftete Integraltypen verwendeten.)Erst kürzlich (C ++ 17?) Wurde der Wortlaut festgelegt, um eine Rundauslösung zu gewährleisten. Dieser Fix, zusammen mit allen anderen Anforderungen an
char
, schreibt effektiv das Zweierkomplement für signiertechar
Zeichen vor, ohne dies ausdrücklich zu sagen (auch wenn der Standard weiterhin Vorzeichen + Größenrepräsentationen für andere signierte Integraltypen zulässt). Es gibt einen Vorschlag, wonach alle signierten Integraltypen das Zweierkomplement verwenden müssen, aber ich kann mich nicht erinnern, ob es in C ++ 20 geschafft hat.Dies ist also genau das Gegenteil von dem, wonach Sie suchen, da es zuvor
falschen,übermäßig anmaßenden Code eine rückwirkende Korrektur gibt.quelle
Ich bin mir nicht sicher, ob Sie dies als eine bahnbrechende Änderung des korrekten Codes betrachten würden, aber ...
Vor C ++ 11 war es Compilern unter bestimmten Umständen gestattet, aber nicht erforderlich, Kopien zu entfernen, selbst wenn der Kopierkonstruktor beobachtbare Nebenwirkungen aufweist. Jetzt haben wir die Kopierentscheidung garantiert. Das Verhalten ging im Wesentlichen von implementierungsdefiniert zu erforderlich über.
Dies bedeutet, dass die Nebenwirkungen Ihres Kopierkonstruktors möglicherweise bei älteren Versionen aufgetreten sind, bei neueren jedoch niemals . Sie könnten argumentieren, dass der richtige Code nicht auf implementierungsdefinierten Ergebnissen beruhen sollte, aber ich denke nicht, dass dies das Gleiche ist, als würde man sagen, dass ein solcher Code falsch ist.
quelle
Das Verhalten beim Lesen (numerischer) Daten aus einem Stream und beim Fehlschlagen des Lesens wurde seit c ++ 11 geändert.
Beispiel: Lesen einer Ganzzahl aus einem Stream, die keine Ganzzahl enthält:
#include <iostream> #include <sstream> int main(int, char **) { int a = 12345; std::string s = "abcd"; // not an integer, so will fail std::stringstream ss(s); ss >> a; std::cout << "fail = " << ss.fail() << " a = " << a << std::endl; // since c++11: a == 0, before a still 12345 }
Da c ++ 11 die gelesene Ganzzahl auf 0 setzt, wenn dies fehlschlägt; Bei c ++ <11 wurde die Ganzzahl nicht geändert. Trotzdem zeigt gcc, selbst wenn der Standard auf c ++ 98 zurückgesetzt wird (mit -std = c ++ 98), zumindest seit Version 4.4.7 immer ein neues Verhalten.
(Imho war das alte Verhalten eigentlich besser: Warum den Wert auf 0 ändern, was für sich genommen gültig ist, wenn nichts gelesen werden konnte?)
Referenz: siehe https://en.cppreference.com/w/cpp/locale/num_get/get
quelle