Welche C ++ - Redewendungen sind in C ++ 11 veraltet?

192

Mit dem neuen Standard gibt es neue Wege, Dinge zu tun, und viele sind schöner als die alten Wege, aber der alte Weg ist immer noch in Ordnung. Es ist auch klar, dass der neue Standard aus Gründen der Abwärtskompatibilität offiziell nicht sehr abwertet. Die Frage bleibt also:

Welche alten Codierungsmethoden sind C ++ 11-Stilen definitiv unterlegen, und was können wir jetzt stattdessen tun?

Wenn Sie darauf antworten, können Sie die offensichtlichen Dinge wie "Auto-Variablen verwenden" überspringen.

Alan Baljeu
quelle
13
Sie können nicht deprecate Idiome.
Pubby
6
Herb Sutters Vortrag auf der Going Native 2012 behandelte dies:
bames53
5
Die Rückgabe konstanter Werte wird nicht mehr empfohlen. Offensichtlich auto_ptrist auch veraltet.
Kerrek SB
27
Natürlich kannst du, Pubby. Bevor C ++ - Vorlagen erfunden wurden, gab es eine Makrotechnik, um Vorlagen zu erstellen. Dann fügte C ++ sie hinzu und der alte Weg wurde als schlecht angesehen.
Alan Baljeu
7
Diese Frage muss wirklich nach Programmers.se verschoben werden.
Nicol Bolas

Antworten:

173
  1. Letzte Klasse : C ++ 11 bietet den finalBezeichner, um die Klassenableitung zu verhindern
  2. C ++ 11-Lambdas reduzieren den Bedarf an benannten Funktionsobjektklassen (Funktorklassen) erheblich.
  3. Move Constructor : Die magischen Methoden, mit denen std::auto_ptrWerke aufgrund der erstklassigen Unterstützung für rvalue-Referenzen nicht mehr benötigt werden.
  4. Safe Bool : Dies wurde bereits erwähnt. Explizite Operatoren von C ++ 11 vermeiden dieses sehr verbreitete C ++ 03-Idiom.
  5. Auf Größe verkleinern : Viele C ++ 11 STL-Container bieten eine shrink_to_fit()Elementfunktion, die das Austauschen mit einer temporären Funktion überflüssig machen sollte.
  6. Temporäre Basisklasse : Einige alte C ++ - Bibliotheken verwenden diese ziemlich komplexe Sprache. Mit der Bewegungssemantik wird es nicht mehr benötigt.
  7. Typensichere Aufzählung Aufzählungen sind in C ++ 11 sehr sicher.
  8. Verbieten der Heap-Zuweisung : Die = deleteSyntax ist eine viel direktere Art zu sagen, dass eine bestimmte Funktionalität explizit verweigert wird. Dies gilt für das Verhindern der Heap-Zuweisung (dh =deletefür Mitglieder operator new), das Verhindern von Kopien, die Zuweisung usw.
  9. Typedef mit Vorlagen : Alias-Vorlagen in C ++ 11 reduzieren die Notwendigkeit einfacher Typedefs mit Vorlagen. Komplexe Typgeneratoren benötigen jedoch weiterhin Metafunktionen.
  10. Einige numerische Berechnungen zur Kompilierungszeit, wie z. B. Fibonacci, können leicht durch verallgemeinerte konstante Ausdrücke ersetzt werden
  11. result_of: Die Verwendung der Klassenvorlage result_ofsollte durch ersetzt werden decltype. Ich denke, result_ofverwendet, decltypewenn es verfügbar ist.
  12. In-Class-Elementinitialisierer speichern die Eingabe für die Standardinitialisierung nicht statischer Elemente mit Standardwerten.
  13. In neuem C ++ 11 NULLsollte Code neu definiert werden als nullptr, aber siehe STLs Vortrag , um zu erfahren, warum sie sich dagegen entschieden haben.
  14. Fanatiker von Ausdrucksvorlagen freuen sich über die Syntax der Trailing-Return- Funktion in C ++ 11. Keine 30 Zeilen langen Rückgabetypen mehr!

Ich denke, ich werde dort aufhören!

Sumant
quelle
Danke für das ausführliche Zeug!
Alan Baljeu
7
Tolle Antwort, aber ich würde result_ofvon der Liste streichen. Trotz der umständlichen Notwendigkeittypename , denke ich, typename result_of<F(Args...)::typekann es manchmal einfacher sein, sie zu lesen als decltype(std::declval<F>()(std::declval<Args>()...), und mit der Aufnahme von N3436 in das Arbeitspapier arbeiten beide für SFINAE (was früher ein Vorteil war decltype, den result_ofdas nicht bot)
Jonathan Wakely
Zu 14) Ich weine immer noch, dass ich Makros verwenden muss, um denselben Code zweimal zu schreiben - einmal für den Funktionskörper und einmal für die Anweisung decltype () ...
2
Ich möchte darauf hinweisen, dass dieses Thema von dieser Microsoft-Seite als Artikel "Weitere Informationen" in einer allgemeinen Einführung in die C ++ - Sprache verlinkt ist , aber dieses Thema ist hochspezialisiert! Darf ich vorschlagen, dass ein kurzes "Dieses Thema ist NICHT für C ++ - Anfänger!" Ratschläge am Anfang des Themas enthalten sein oder diese Antwort?
Aacini
Zu 12: "Initialisierung von Mitgliedern in der Klasse" - das ist die neue Redewendung, keine veraltete Redewendung, nicht wahr? Satzreihenfolge vielleicht wechseln? Zu 2: Funktoren sind sehr nützlich, wenn Sie Typen anstelle von Objekten weitergeben möchten (insbesondere in Vorlagenparametern). Es sind also nur einige Anwendungen von Funktoren, die veraltet sind.
Einpoklum
66

Zu einem bestimmten Zeitpunkt wurde argumentiert, dass man nach constWert anstatt nur nach Wert zurückkehren sollte:

const A foo();
^^^^^

Dies war in C ++ 98/03 größtenteils harmlos und hat möglicherweise sogar einige Fehler entdeckt, die wie folgt aussahen:

foo() = a;

Die Rückkehr durch constist in C ++ 11 jedoch kontraindiziert, da sie die Bewegungssemantik verhindert:

A a = foo();  // foo will copy into a instead of move into it

Also einfach entspannen und codieren:

A foo();  // return by non-const value
Howard Hinnant
quelle
9
Die vermeidbaren Fehler können jetzt jedoch mithilfe von Referenzqualifizierern für Funktionen abgefangen werden. Wie im obigen Fall A& operator=(A o)&anstelle von definieren A& operator=(A o). Diese verhindern die dummen Fehler und führen dazu, dass sich Klassen eher wie Basistypen verhalten und die Bewegungssemantik nicht verhindern.
Joe
61

Sobald Sie aufgeben können 0und NULLdafür sind nullptr, tun Sie dies!

In nicht generischem Code ist die Verwendung von 0oder NULLnicht so eine große Sache. Sobald Sie jedoch anfangen, Nullzeigerkonstanten im generischen Code zu übergeben, ändert sich die Situation schnell. Wenn Sie 0an a übergeben, template<class T> func(T) Twird dies als intund nicht als Nullzeigerkonstante abgeleitet. Danach kann es nicht mehr in eine Nullzeigerkonstante konvertiert werden. Dies führt zu einem Sumpf von Problemen, die einfach nicht existieren, wenn das Universum nur verwendet wird nullptr.

C ++ 11 ist nicht veraltet 0und NULLals Nullzeigerkonstanten. Aber Sie sollten so codieren, als ob es so wäre.

Howard Hinnant
quelle
Was ist Decltype (nullptr)?
4
@GrapschKnutsch: Das ist es std::nullptr_t.
Howard Hinnant
Schlagen Sie vor, dies neu zu formulieren, da die Redewendung veraltet ist und nicht die neue Konvention (z. B. "Verwendung von 0oder NULLfür Nullzeiger").
Einpoklum
38

Sichere Bool-Spracheexplicit operator bool().

Konstrukteure für private Kopien (boost :: noncopyable) → X(const X&) = delete

Simulation der letzten Klasse mit privatem Destruktor und virtueller Vererbungclass X final

kennytm
quelle
gute und prägnante Beispiele, von denen eines sogar das Wort "Redewendung" enthält. gut ausgedrückt
Sebastian Mach
2
Wow, ich habe noch nie die "sichere Bool-Sprache" gesehen, sie sieht ziemlich ekelhaft aus! Ich hoffe, ich brauche es nie in Code vor C ++ 11 ...
Boycy
24

Eines der Dinge, die Sie nur vermeiden, grundlegende Algorithmen in C ++ 11 zu schreiben, ist die Verfügbarkeit von Lambdas in Kombination mit den von der Standardbibliothek bereitgestellten Algorithmen.

Ich benutze diese jetzt und es ist unglaublich, wie oft Sie einfach sagen, was Sie tun möchten, indem Sie count_if (), for_each () oder andere Algorithmen verwenden, anstatt die verdammten Schleifen erneut schreiben zu müssen.

Sobald Sie einen C ++ 11-Compiler mit einer vollständigen C ++ 11-Standardbibliothek verwenden, haben Sie keine gute Entschuldigung mehr, keine Standardalgorithmen zum Erstellen Ihrer zu verwenden . Lambda töte es einfach.

Warum?

In der Praxis (nachdem ich diese Art des Schreibens von Algorithmen selbst verwendet habe) fühlt es sich viel einfacher an, etwas zu lesen, das aus einfachen Wörtern besteht, die bedeuten, was getan wird, als mit einigen Schleifen, die Sie entschlüsseln müssen, um die Bedeutung zu kennen. Das automatische Ableiten von Lambda-Argumenten würde jedoch viel dazu beitragen, die Syntax leichter mit einer Raw-Schleife vergleichbar zu machen.

Grundsätzlich sind Lesealgorithmen, die mit Standardalgorithmen erstellt wurden, viel einfacher als Wörter, die die Implementierungsdetails der Schleifen verbergen.

Ich vermute, dass nur übergeordnete Algorithmen berücksichtigt werden müssen, da wir Algorithmen auf niedrigerer Ebene haben, auf denen wir aufbauen können.

Klaim
quelle
8
Eigentlich gibt es eine gute Ausrede. Sie verwenden die Algorithmen von Boost.Range , die viel besser sind;)
Nicol Bolas
10
Ich sehe nicht, dass for_eachein Lambda besser ist als die äquivalente bereichsbasierte for-Schleife, mit dem Inhalt des Lambda in der Schleife. Der Code sieht mehr oder weniger gleich aus, aber das Lambda führt eine zusätzliche Interpunktion ein. Sie können Äquivalente von Dingen verwenden boost::irange, um sie auf mehr Schleifen anzuwenden als nur auf solche, die offensichtlich Iteratoren verwenden. Darüber hinaus bietet die bereichsbasierte for-Schleife eine größere Flexibilität, da Sie bei Bedarf (nach returnoder nach break) vorzeitig beenden können , während for_eachSie mit werfen müssen.
Steve Jessop
5
@SteveJessop: Trotzdem formacht die Verfügbarkeit von Range-Based die übliche it = c.begin(), const end = c.end(); it != end; ++itRedewendung ungültig.
Ben Voigt
7
@SteveJessop Ein Vorteil des for_eachAlgorithmus gegenüber dem für die Schleife basierenden Bereich ist, dass Sie nicht break oder können return. Das heißt, wenn Sie sehen, dass for_eachSie sofort wissen, ohne auf den Körper zu schauen, dass es keine solche Schwierigkeit gibt.
Bames53
5
@Klaim: um genau zu sein, vergleiche ich zum Beispiel std::for_each(v.begin(), v.end(), [](int &i) { ++i; });mit for (auto &i : v) { ++i; }. Ich akzeptiere, dass Flexibilität zweischneidig ist ( gotoist sehr flexibel, das ist das Problem). Ich glaube nicht , dass die Einschränkung von nicht verwenden zu können , breakin der for_eachVersion kompensiert die zusätzliche Ausführlichkeit es verlangt - Nutzer for_eachhier IMO tatsächliche Lesbarkeit und Komfort für eine Art theoretischer Vorstellung opfern , dass das for_eachist im Prinzip klarer und konzeptuell einfacher. In der Praxis ist es nicht klarer oder einfacher.
Steve Jessop
10

Sie müssen swapweniger häufig benutzerdefinierte Versionen implementieren . In C ++ 03 swapist häufig ein effizientes Nicht-Werfen erforderlich, um kostspielige und werfende Kopien zu vermeiden. Da std::swapzwei Kopien verwendet werden, muss dies swaphäufig angepasst werden. In C ++ std::swapAnwendungen move, und so verschiebt sich der Fokus auf die Implementierung effizienter und nicht werfen bewegen Bauer und bewegen Zuweisungsoperatoren. Da für diese der Standard oft in Ordnung ist, ist dies viel weniger Arbeit als in C ++ 03.

Im Allgemeinen ist es schwer vorherzusagen, welche Redewendungen verwendet werden, da sie durch Erfahrung entstehen. Wir können ein "Effective C ++ 11" vielleicht nächstes Jahr und einen "C ++ 11 Coding Standards" erst in drei Jahren erwarten, da die notwendige Erfahrung noch nicht vorhanden ist.

Philipp
quelle
1
Das bezweifle ich. Der empfohlene Stil ist die Verwendung von Swap für die Verschiebungs- und Kopierkonstruktion, nicht jedoch von std :: swap, da dies kreisförmig wäre.
Alan Baljeu
Ja, aber der Verschiebungskonstruktor ruft normalerweise einen benutzerdefinierten Austausch auf, oder er ist im Wesentlichen gleichwertig.
Inverse
2

Ich kenne den Namen nicht, aber C ++ 03-Code verwendete häufig das folgende Konstrukt als Ersatz für fehlende Verschiebungszuweisungen:

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

Dies verhinderte jegliches Kopieren aufgrund von Kopierentfernung in Kombination mit dem swapObigen.

Andrzej
quelle
1
In Ihrem Beispiel ist der Swap nicht erforderlich. Copy Elision würde den Rückgabewert maptrotzdem konstruieren . Die Technik, die Sie zeigen, ist nützlich, wenn sie mapbereits vorhanden ist, anstatt nur konstruiert zu werden. Das Beispiel wäre besser ohne den Kommentar "billiger Standardkonstruktor" und mit "// ..." zwischen dieser Konstruktion und dem Tausch
Jonathan Wakely
Ich habe es gemäß Ihrem Vorschlag geändert. Vielen Dank.
Andrzej
Die Verwendung von "groß" und "größer" ist verwirrend. Warum nicht erklären, wie wichtig die Größe des Schlüssels und der Werttyp sind?
Einpoklum
1

Als ich bemerkte, dass ein Compiler, der den C ++ 11-Standard verwendet, den folgenden Code nicht mehr fehlerhaft macht:

std::vector<std::vector<int>> a;

für angeblich mit Operator >> begann ich zu tanzen. In den früheren Versionen müsste man tun

std::vector<std::vector<int> > a;

Um die Sache noch schlimmer zu machen, wenn Sie dies jemals debuggen mussten, wissen Sie, wie schrecklich die Fehlermeldungen sind, die daraus entstehen.

Ich weiß jedoch nicht, ob dies für Sie "offensichtlich" war.

v010dya
quelle
1
Diese Funktion wurde bereits in der vorherigen C ++ hinzugefügt. Oder zumindest Visual C ++ hat es viele Jahre zuvor gemäß der Standarddiskussion implementiert.
Alan Baljeu
1
@AlanBaljeu Natürlich werden dem Compiler / den Bibliotheken viele nicht standardmäßige Dinge hinzugefügt. Es gab Unmengen von Compilern, die vor C ++ 11 eine "automatische" Variablendeklaration hatten, aber dann konnten Sie nicht sicher sein, ob Ihr Code tatsächlich von irgendetwas anderem kompiliert werden kann. Die Frage betraf den Standard und nicht "Gab es einen Compiler, der dies tun konnte".
v010dya
1

Die Rückgabe nach Wert ist kein Problem mehr. Mit der Bewegungssemantik und / oder der Optimierung des Rückgabewerts (compilerabhängig) sind Codierungsfunktionen natürlicher, ohne Overhead oder Kosten (meistens).

Martin A.
quelle
... aber welche Redewendung ist veraltet?
Einpoklum
Keine Redewendung, aber es war eine gute Übung, die nicht mehr benötigt wurde. Auch mit vom Compiler unterstützter RVO, die optional ist. en.wikipedia.org/wiki/Return_value_optimization "In den frühen Stadien der Entwicklung von C ++ wurde die Unfähigkeit der Sprache, ein Objekt vom Klassentyp effizient von einer Funktion zurückzugeben, als Schwäche angesehen ....." struct Data {char bytes [ 16]; }; void f (Daten * p) {// Ergebnis direkt in * p} generieren int main () {Daten d; f (& d); }
Martin A
Ich habe angedeutet, dass Sie Ihre Antwort so formulieren sollten: "Der Brauch, Wertrückgaben zu vermeiden, ist nicht mehr relevant, da usw. usw. usw."
Einpoklum