Angesichts des neuen Toolset, das von vielen Programmierern in C ++ bereitgestellt wird, zielen viele auf Programmvereinfachung, Ausdruckskraft und Effizienz ab, überfliegen ihren alten Code und nehmen Optimierungen (einige sinnlos, andere erfolgreich) vor, um ihre Ziele zu erreichen. Was sind die besten Praktiken, wenn Sie versuchen, nicht zu viel Zeit für solche Arbeiten zu verlieren und nur nicht aufdringliche und in sich geschlossene Änderungen vorzunehmen?
Lassen Sie mich das Offensichtliche streichen:
Verwenden Sie auto , um iteratorbasierte Schleifen auszuführen:
for (std::vector<foo>::const_iterator it(lala.begin()), ite(lala.end()); it != ite; ++it); // becomes for (auto it(lala.cbegin()), ite(lala.cend()); it != ite; ++it);
Verwenden Sie tie für mehrere Zuweisungen, die nur Codezeilen im C-Stil erzeugen ( wie können Sie einer Struktur mehrere Werte gleichzeitig zuweisen? )
a = 1; b = 2; c = 3; d = 4; e = 5; // becomes std::tie(a, b, c, d, e) = std::make_tuple(1, 2, 3, 4, 5);
Um eine Klasse nicht vererbbar zu machen, deklarieren Sie sie einfach als "final" und löschen Sie den Code, der ein solches Verhalten erreicht hat: http://www.parashift.com/c++-faq/final-classes.html
Verwenden Sie das Schlüsselwort delete, um Konstruktoren / Destruktoren explizit auszublenden, anstatt sie als privat zu deklarieren (z. B. Code zum Erstellen von Heap-basierten Objekten, nicht kopierbaren Objekten usw.).
Verwandeln Sie triviale Funktoren, die nur erstellt wurden, um die Ausführung eines einzelnen STL-Algorithmus zu vereinfachen, in Lambda- Funktionen (abgesehen von der Reduzierung der Code-Unordnung haben Sie garantiert Inline-Aufrufe).
Vereinfachen Sie das RAII-Wrapping eines Objekts mit einem intelligenten Zeiger
Befreien Sie sich von bind1st, bind2nd und verwenden Sie einfach bind
Ersetzen Sie handgeschriebenen Code für Typmerkmale (Is_ptr_but_dont_call_for_const_ptrs <> und dergleichen :)) durch Standardcode, der von <type_traits> bereitgestellt wird
Hören Sie auf, Boost-Header für die jetzt in STL implementierte Funktionalität einzuschließen (BOOST_STATIC_ASSERT vs static_assert).
Stellen Sie Verschiebungssemantik für Klassen bereit (obwohl dies nicht als schmutzige / schnelle / einfache Änderung qualifiziert wäre)
Verwenden Sie nach Möglichkeit nullptr anstelle des NULL-Makros und entfernen Sie den Code, der Container mit Zeigern mit Nullen füllte, die in den Objekttyp umgewandelt wurden
std::vector<foo*> f(23); for (std::size_t i(0); i < 23; ++i) { f[i] = static_cast<foo*>(0); } // becomes std::vector<foo*> f(23, nullptr);
Löschen Sie die Syntax für den Zugriff auf Vektordaten
std::vector<int> vec; &vec[0]; // access data as a C-style array vec.data(); // new way of saying the above
Ersetzen Sie throw () durch noexcept (abgesehen von der Vermeidung der veralteten Ausnahmespezifikation erhalten Sie einige Geschwindigkeitsvorteile http://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler @ 00.29.42)
void some_func() noexcept; // more optimization options void some_func() throw(); // fewer optimization options void some_func() ; // fewer optimization options
Ersetzen Sie Code, bei dem Sie ein Tempory in einen Container verschieben würden, und hoffen Sie, dass der Optimierer die Kopie mit einer "Emplace" -Funktion entfernt, sofern verfügbar, um das Argument perfekt weiterzuleiten und ein Objekt direkt in einen Container ohne temporäres at zu konstruieren alle.
vecOfPoints.push_back(Point(x,y,z)); // so '03 vecOfPoints.emplace_back(x, y, z); // no copy or move operations performed
AKTUALISIEREN
Die Antwort von Shafik Yaghmour wurde zu Recht mit dem Kopfgeld für die größte Akzeptanz beim Publikum ausgezeichnet.
Die Antwort von R Sahu war meine akzeptierte, weil die Kombination der vorgeschlagenen Funktionen den Geist des Refactorings einfängt : Code klarer und sauberer und einfacher und eleganter machen.
quelle
.data()
anstelle von&container[0]
. Es funktioniert NICHT,std::string
wenn Sie die internen Daten ändern möchten. Warum, weil.data()
forstd::string
dasselbe ist wie.c_str()
und einen konstanten Zeiger zurückgibt. Auch für MSVC2013,push_back
nimmt einT&&
und ist das gleiche wieemplace_back
.override
diese Option, um anzugeben, dass eine Funktion eine virtuelle Funktion in der Basisklasse überschreibt, anstatt eine neue Funktion zum Ausblenden in der Basisklasse einzuführen. Ich würde auch davon abraten, jede Klasse zu einem Finale zu machen. Es sollte sparsam verwendet werden, da es das Testen des Codes schmerzhafter machen kann, als es sein muss.Antworten:
Ich würde der Liste delegierende Konstruktoren und Initialisierer für Mitglieder in der Klasse hinzufügen.
Vereinfachung durch Delegieren von Konstruktoren und In-Class-Initialisierung
Mit C ++ 03:
class A { public: // The default constructor as well as the copy constructor need to // initialize some of the members almost the same and call init() to // finish construction. A(double data) : id_(0), name_(), data_(data) {init();} A(A const& copy) : id_(0), name_(), data_(copy.data_) {init();} void init() { id_ = getNextID(); name_ = getDefaultName(); } int id_; string name_; double data_; };
Mit C ++ 11:
class A { public: // With delegating constructor, the copy constructor can // reuse this constructor and avoid repetitive code. // In-line initialization takes care of initializing the members. A(double data) : data_(data) {} A(A const& copy) : A(copy.data_) {} int id_ = getNextID(); string name_ = getDefaultName(); double data_; };
quelle
1. Rand ersetzen
Einer der großen Vorteile in C ++ 11 muss darin bestehen, die Verwendung von
rand()
durch alle im Zufallskopf verfügbaren Optionen zu ersetzen . Das Ersetzen sollterand()
in vielen Fällen einfach sein.Stephan T. Lavavej hat diesen Punkt wahrscheinlich mit seiner Präsentation rand () als am schädlichsten herausgestellt . Die Beispiele zeigen eine einheitliche Ganzzahlverteilung bei
[0,10]
Verwendung vonrand()
:#include <cstdlib> #include <iostream> #include <ctime> int main() { srand(time(0)) ; for (int n = 0; n < 10; ++n) { std::cout << (rand() / (RAND_MAX / (10 + 1) + 1)) << ", " ; } std::cout << std::endl ; }
und mit std :: uniform_int_distrubution :
#include <iostream> #include <random> int main() { std::random_device rd; std::mt19937 e2(rd()); std::uniform_int_distribution<> dist(0, 10); for (int n = 0; n < 10; ++n) { std::cout << dist(e2) << ", " ; } std::cout << std::endl ; }
Gleichzeitig sollte von std :: random_shuffle zu std :: shuffle gewechselt werden , was sich aus der Anstrengung ergibt, Rand und Freunde zu verwerfen . Dies wurde kürzlich in der SO-Frage behandelt. Warum werden std :: shuffle-Methoden in C ++ 14 nicht mehr unterstützt? .
Beachten Sie, dass nicht garantiert wird, dass die Distributionen plattformübergreifend konsistent sind .
2. Verwenden Sie std :: to_string anstelle von std :: ostringstream oder sprintf
C ++ 11 bietet std :: to_string, mit dem Zahlen in std :: string konvertiert werden können. Der Inhalt wird als äquivalentes std :: sprintf erzeugt . Höchstwahrscheinlich würde dies anstelle von std :: ostringstream oder verwendet
snprintf
. Dies ist eher eine Annehmlichkeit, es gibt wahrscheinlich keinen großen Leistungsunterschied und wir können anhand der schnellen Ganzzahl- zu Zeichenfolgenkonvertierung im C ++ - Artikel sehen, dass wahrscheinlich viel schnellere Alternativen verfügbar sind, wenn die Leistung das Hauptanliegen ist:#include <iostream> #include <sstream> #include <string> int main() { std::ostringstream mystream; mystream << 100 ; std::string s = mystream.str(); std::cout << s << std::endl ; char buff[12] = {0}; sprintf(buff, "%d", 100); std::string s2( buff ) ; std::cout << s2 << std::endl ; std::cout << std::to_string( 100 ) << std::endl ; }
3. Verwenden von constexpr anstelle der Vorlagen-Metaprogrammierung
Wenn Sie mit Literalen arbeiten, kann es Fälle geben, in denen die Verwendung von constexpr-Funktionen über die Metaprogrammierung von Vorlagen zu Code führt, der klarer ist und möglicherweise schneller kompiliert wird. Der Artikel Willst du Geschwindigkeit? Verwenden Sie constexpr Meta-Programmierung! bietet ein Beispiel für die Bestimmung von Primzahlen mithilfe der Metaprogrammierung von Vorlagen:
struct false_type { typedef false_type type; enum { value = 0 }; }; struct true_type { typedef true_type type; enum { value = 1 }; }; template<bool condition, class T, class U> struct if_ { typedef U type; }; template <class T, class U> struct if_<true, T, U> { typedef T type; }; template<size_t N, size_t c> struct is_prime_impl { typedef typename if_<(c*c > N), true_type, typename if_<(N % c == 0), false_type, is_prime_impl<N, c+1> >::type >::type type; enum { value = type::value }; }; template<size_t N> struct is_prime { enum { value = is_prime_impl<N, 2>::type::value }; }; template <> struct is_prime<0> { enum { value = 0 }; }; template <> struct is_prime<1> { enum { value = 0 }; };
und Verwenden von constexpr-Funktionen:
constexpr bool is_prime_recursive(size_t number, size_t c) { return (c*c > number) ? true : (number % c == 0) ? false : is_prime_recursive(number, c+1); } constexpr bool is_prime_func(size_t number) { return (number <= 1) ? false : is_prime_recursive(number, 2); }
Die constexpr-Version ist viel kürzer, leichter zu verstehen und anscheinend viel besser als die Implementierung der Vorlagen-Metaprogrammierung.
4. Verwenden der Klassenelementinitialisierung zum Bereitstellen von Standardwerten
Wie kürzlich in Hat die neue Initialisierungsfunktion für C ++ 11-Mitglieder bei der Deklaration die Initialisierungslisten überflüssig gemacht? Die Initialisierung von Klassenmitgliedern kann verwendet werden, um Standardwerte bereitzustellen, und kann Fälle vereinfachen, in denen eine Klasse mehrere Konstruktoren hat.
Bjarne Stroustrup liefert ein gutes Beispiel in den C ++ 11-FAQ, sagt er:
und bietet ein Beispiel für Mitglieder mit einem gemeinsamen Initialisierer:
class A { public: A(): a(7), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(D d) : a(7), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {} int a, b; private: HashingFunction hash_algorithm; // Cryptographic hash to be applied to all A instances std::string s; // String indicating state in object lifecycle };
und sagt:
class A { public: A(): a(7), b(5) {} A(int a_val) : a(a_val), b(5) {} A(D d) : a(7), b(g(d)) {} int a, b; private: HashingFunction hash_algorithm{"MD5"}; // Cryptographic hash to be applied to all A instances std::string s{"Constructor run"}; // String indicating state in object lifecycle };
Beachten Sie, dass in C ++ 11 eine Klasse, die Initialisierer für Klassenmitglieder verwendet, kein Aggregat mehr ist, obwohl diese Einschränkung in C ++ 14 aufgehoben wurde.
5. Verwenden Sie Integer-Typen mit fester Breite von cstdint anstelle von handgerollten Typedefs
Da der C ++ 11-Standard C99 als normative Referenz verwendet, erhalten wir auch Ganzzahltypen mit fester Breite . Zum Beispiel:
int8_t int16_t int32_t int64_t intptr_t
Obwohl einige davon optional sind, gilt für die Ganzzahltypen mit exakter Breite Folgendes aus dem Abschnitt C99
7.18.1.1
:quelle
Für jede Syntax:
std::vector<int> container; for (auto const & i : container) std::cout << i << std::endl;
quelle
std::endl
bewirkt eine Pufferleerung in den Konsolenpuffer. Ist oft"\n"
viel schneller.std::map
aufstd::unordered_map
undstd::set
zu ,std::unordered_set
wo immer Reihenfolge der Elemente des Containers irrelevant ist, verbessert erheblich die Leistung.std::map::at
anstelle der Einfügung von eckigen Klammern die Syntaxeinfügung, wenn Sie unfreiwillige Einfügungen vermeiden möchten.typedef
.quelle
std::uordered_set
-> hier ist ein Tippfehler. 1. Diese Änderung kann die Leistung nur für Karten und Sets verbessern, die groß genug sind (andernfalls wird nur die Effizienz des Programms verringert). Es könnte auch die Leistung verringern, wenn über solche Container iteriert wird.Verwenden Sie die einheitliche Initialisierungssyntax für die Variableninitialisierung
widget w(x); // old widget w{x}; // new
Um Probleme wie die ärgerlichste Analyse von c ++ zu vermeiden (die restlichen Gründe, warum der neue Weg überlegen ist, werden im verlinkten Artikel von Herb Sutter erläutert).
quelle
widget
gibt einen Konstruktor, der dies erfordertstd::initializer_list
.In diesem Blog-Beitrag wird die Null-Regel vorgeschlagen, wenn alle Eigentümer einer Klasse dem RAII-Prinzip folgen und die Regel Drei / Vier / Fünf in C ++ 11 loswerden können.
Scott Meyers zeigt hier jedoch, dass das nicht explizite Schreiben des Destruktors, der Kopier- / Verschiebungskonstruktoren und der Zuweisungsoperatoren subtile Probleme verursachen kann, wenn Sie Ihren Code geringfügig ändern (z. B. zum Debuggen). Anschließend empfiehlt er, diese Funktionen explizit als Standard (C ++ 11-Funktion) zu deklarieren :
~MyClass() = default; MyClass( const MyClass& ) = default; MyClass( MyClass&& ) = default; MyClass& operator=( const MyClass& ) = default; MyClass& operator=( MyClass&& ) = default;
quelle
Feature: std :: move
"Drücken Sie einen deutlichen Unterschied zwischen dem Kopieren und Verschieben der Ressourcen aus"
std::string tmp("move"); std::vector<std::string> v; v.push_back(std::move(tmp)); //At this point tmp still be the valid object but in unspecified state as // its resources has been moved and now stored in vector container.
quelle
tmp
es sich nach dem Verschieben "im leeren Zustand" befindet, nur dass es sich um einen "gültigen, aber nicht spezifizierten" Zustand handelt. (In der Tat wird eine qualitativ hochwertige String-Implementierung mit Optimierung kleiner Objekte wahrscheinlichtmp
unverändert bleiben.)Ziehen Sie Enums mit Gültigkeitsbereich Scope vor
In C ++ 98 gibt es keine Aufzählungen für Aufzählungen wie das folgende Code-Snippet. Die Namen solcher Enumeratoren gehören zu dem Bereich, der Enum enthält, und nichts anderes in diesem Bereich darf denselben Namen haben.
enum Color{ blue, green, yellow }; bool blue = false; // error: 'blue' redefinition
In C ++ 11
scoped enums
kann das Problem jedoch behoben werden.scoped enum
werden als var deklariertenum class
.enum class Color{ blue, green, yellow }; bool blue = false; // fine, no other `blue` in scope Color cc = blue; // error! no enumerator `blue` in this scope Color cc = Color::blue; // fine auto c = Color::blue; // fine
Die Enumeratoren von
scope enums
sind stärker typisiert. Die Enumeratoren vonunscoped enums
konvertieren jedoch implizit in andere Typenenum Color{ blue, green, yellow }; std::vector<std::size_t> getVector(std::size_t x); Color c = blue; if (c < 10.1) { // compare Color with double !! auto vec = getVector(c); // could be fine !! }
Allerdings
scoped enums
wird in diesem Fall nicht bestanden.enum class Color{ blue, green, yellow }; std::vector<std::size_t> getVector(std::size_t x); Color c = Color::blue; if (c < 10.1) { // error ! auto vec = getVector(c); // error !! }
Repariere es durch
static_cast
if (static_cast<double>(c) < 10.1) { auto vec = getVector(static_cast<std::size_t>(c)); }
unscoped enums
kann vorwärts deklariert werden.enum Color; // error!! enum class Color; // fine
Beide
scoped
undunscoped
Aufzählungen unterstützen die Angabe des zugrunde liegenden Typs. Der zugrunde liegende Standardtyp fürscoped enums
istint
.Unscoped enums
haben keinen zugrunde liegenden Standardtyp.Verwenden der Parallelitäts-API
Bevorzugen Sie aufgabenbasiert gegenüber threadbasiert
Wenn Sie eine Funktion
doAsyncWork
asynchron ausführen möchten , haben Sie zwei grundlegende Möglichkeiten. Einer ist threadbasiertint doAsyncWork(); std::thread t(doAsyncWork);
Der andere ist aufgabenbasiert .
auto fut = std::async(doAsyncWork);
Natürlich können wir den Rückgabewert erhalten
doAsyncWork
durch taskbasierte leichter als faden basiert . Mit demtask-based
Ansatz ist es einfach, weil die Zukunft, die von zurückgegebenstd::async
wird, die get-Funktion bietet. Dieget
Funktion ist noch wichtiger, wenndoAsyncWork
eine Ausnahme ausgegeben wird, da auchget
der Zugriff darauf möglich ist.Thread-based
fordert eine manuelle Verwaltung der Thread-Erschöpfung, Überbelegung, des Lastausgleichs und der Anpassung an neue Plattformen. AberTask-based
überstd::async
den Standardstart Politik leidet an keiner dieser Nachteile.Hier sind mehrere Links:
Parallelität in C ++
C / C ++ - Programmierabstraktionen für Parallelität und Parallelität
quelle
Optimieren Sie einfache mathematische Funktionen mit constexpr, insbesondere wenn sie in inneren Schleifen aufgerufen werden. Auf diese Weise kann der Compiler sie bei der Kompilierung berechnen, wodurch Sie Zeit sparen
Beispiel
constexpr int fibonacci(int i) { return i==0 ? 0 : (i==1 ? 1 : fibonacci(i-1) + fibonacci(i-2)); }
Ein anderes Beispiel ist zu verwenden
std::enable_if
Begrenzung der zulässigen Vorlagenparametertypen in einer bestimmten Vorlagenfunktion / -klasse. Dies würde Ihren Code sicherer machen (falls Sie SFINAE nicht verwendet haben, um die möglichen Vorlagenargumente in Ihrem alten Code einzuschränken), wenn Sie implizit eine Eigenschaft über die Vorlagentypen annehmen und es sich nur um eine zusätzliche Codezeile handeltBeispiel:
template < typename T, std::enable_if< std::is_abstract<T>::value == false, bool>::type = false // extra line > void f(T t) { // do something that depends on the fact that std::is_abstract<T>::value == false }
Update 1: Wenn Sie ein kleines Array haben, dessen Größe zur Kompilierungszeit bekannt ist, und Sie den Overhead der Heap-Zuordnung in std :: vector vermeiden möchten (dh, Sie möchten das Array auf dem Stapel haben), wählen Sie nur in C ++ 03 sollte Arrays im C-Stil verwenden. Ändern Sie das in
std::array
. Es ist eine einfache Änderung, die Ihnen einen Großteil der Funktionen bietet, die in der std :: vector + -Stack-Zuweisung vorhanden sind (viel schneller als die Heap-Zuweisung, wie ich bereits sagte).quelle
Verwenden Sie intelligente Zeiger. Beachten Sie, dass es in einigen Fällen immer noch gute Gründe gibt, nackte Zeiger zu haben. Der beste Weg, um zu überprüfen, ob ein Zeiger intelligent sein sollte, besteht darin, nach den Verwendungsmöglichkeiten von zu suchen
delete
.Es sollte auch keinen Grund geben, dies zu verwenden
new
. Ersetzen Sie allenew
durchmake_shared
odermake_unique
.Leider
make_unique
nicht im C ++ 11-Standard , die beste Lösung IMO ist es, es selbst zu implementieren ( siehe vorherigen Link ) und einige Makros zu setzen, um nach der__cplusplus
Version zu suchen (make_unique
ist in C ++ 14 verfügbar).Die Verwendung von
make_unique
undmake_shared
ist sehr wichtig, um Ihre Code-Ausnahme sicher zu machen.quelle
Verwendung von Override Stichwort
Markieren Sie virtuelle Funktionen in abgeleiteten Klassen als Überschreibung (wenn sie tatsächlich überschreiben). Dies kann vor künftigen Fehlern schützen, z. B. indem die Signatur einer virtuellen Funktion in einer Basisklasse geändert wird und vergessen wird, die Signatur in allen abgeleiteten Klassen entsprechend zu ändern.
quelle