Refactoring mit C ++ 11

70

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.

Nikos Athanasiou
quelle
22
Bitte schließen Sie das nicht. es ist wirklich nützlich.
Karoly Horvath
2
Ich sehe nicht, wie dies "hauptsächlich meinungsbasiert" ist. Überhaupt. Es ist jedoch eine dieser Fragen der großen Liste, die auch nicht wirklich zum Stapelüberlaufformat passen.
Konrad Rudolph
2
Mögliches Duplikat dessen, welche C ++ - Redewendungen in C ++ 11
TemplateRex
4
Es gibt einen Haken bei der Verwendung von .data()anstelle von &container[0]. Es funktioniert NICHT, std::stringwenn Sie die internen Daten ändern möchten. Warum, weil .data()for std::stringdasselbe ist wie .c_str()und einen konstanten Zeiger zurückgibt. Auch für MSVC2013, push_backnimmt ein T&&und ist das gleiche wie emplace_back.
Brandon
5
Verwenden Sie overridediese 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.
sdkljhdf hda

Antworten:

19

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_;
};
R Sahu
quelle
29

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 sollte rand()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 von rand():

#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:

Dies spart ein wenig Eingabe, aber die wirklichen Vorteile ergeben sich aus Klassen mit mehreren Konstruktoren. Häufig verwenden alle Konstruktoren einen gemeinsamen Initialisierer für ein Mitglied:

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:

Die Tatsache, dass hash_algorithm und s jeweils einen einzigen Standard haben, geht im Code-Durcheinander verloren und kann während der Wartung leicht zu einem Problem werden. Stattdessen können wir die Initialisierung der Datenelemente herausrechnen:

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:

Diese Typen sind optional. Wenn eine Implementierung jedoch ganzzahlige Typen mit Breiten von 8, 16, 32 oder 64 Bit, keine Auffüllbits und (für die vorzeichenbehafteten Typen) mit einer Zweierkomplementdarstellung bereitstellt, muss sie die entsprechenden typedef-Namen definieren.

Shafik Yaghmour
quelle
1
Anfangs schien es ein bisschen übertrieben zu sein, aber nachdem ich mir die Präsentation angesehen hatte, gab ich zu, dass es viele Probleme gab, die mir nicht bewusst waren. Großartiger Code für diesen.
Nikos Athanasiou
@NikosAthanasiou In Anbetracht Ihrer Prämie liefert meine Antwort nicht genügend Details? Wenn ja, welche Details möchten Sie sehen?
Shafik Yaghmour
Das Kopfgeld wurde angeboten, um mehr Antworten und aufgedeckte Techniken zu motivieren und mehr Leute dazu zu bringen, zu kommentieren, was sie verwenden und was funktioniert oder nicht. Ihre Antwort ist sowohl gründlich als auch nützlich. Ich denke nicht, dass es verbessert werden muss und so wie es aussieht, wird es das beliebteste bleiben und das Kopfgeld gewinnen
Nikos Athanasiou
@ Nikos Athanasiou stimmte voll und ganz zu. Ich mag seine Antwort. Wenn dies passiert ist (Sie haben ein anderes Beispiel, um eine Antwort zu ergänzen und / oder zu aktualisieren), würde ich normalerweise nur ein Zitat wie "Um die vorherige Antwort von Vin zu ergänzen ... hier ist ..." geben. Vergessen Sie jedoch nicht, dass vor C ++ 11 Dinge wie Fibbonaci-Berechnung oder pow oder kleine Zahlen mithilfe der Metaprogrammierung von Vorlagen ausgeführt wurden, sodass unsere Antworten nicht so unterschiedlich sind.
Vivian Miranda
12

Für jede Syntax:

std::vector<int> container;

for (auto const & i : container)
  std::cout << i << std::endl;
Voodooattack
quelle
2
Ich kann nicht verstehen, warum die Leute immer noch Iteratoren bevorzugen, die auf Schleifen basieren, for_each mit Lambdas usw. Der Bereich, der auf Schleifen basiert, führt zu weniger Code, und es ist offensichtlich einfacher zu lesen.
Murat Şeker
1
@ MuratŞeker: Es ist viel einfacher, wenn Sie zwei Iteratoren für zwei gleich große Container haben.
MSalters
2
Nun, Sie können keine bereichsbasierte Iteration verwenden, wenn Sie den Container beim Durchlaufen ändern müssen, und Iteratoren bieten im Allgemeinen mehr Kontrolle. Ansonsten sehe ich keinen Grund, sie nicht zu benutzen.
Voodooattack
1
In stackoverflow.com/questions/21517399/… werden zwei Nachteile von Fernkampf für erwähnt. Trotzdem ist es in der Tat eine gute Praxis für nicht komplizierte Schleifen, den "neuen" Weg zu verwenden.
Nikos Athanasiou
Übrigens std::endlbewirkt eine Pufferleerung in den Konsolenpuffer. Ist oft "\n"viel schneller.
Gradbot
11
  1. Ändern std::mapauf std::unordered_mapund std::setzu , std::unordered_setwo immer Reihenfolge der Elemente des Containers irrelevant ist, verbessert erheblich die Leistung.
  2. Verwenden Sie std::map::atanstelle der Einfügung von eckigen Klammern die Syntaxeinfügung, wenn Sie unfreiwillige Einfügungen vermeiden möchten.
  3. Verwenden Sie Alias-Vorlagen, wenn Sie Vorlagen erstellen möchten typedef.
  4. Verwendung von Initialisierungslisten anstelle von for-Schleifen zum Initialisieren von STL-Containern.
  5. Ersetzen Sie C-Arrays mit fester Größe durch std :: array.
101010
quelle
5
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.
Konstruktor
9

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;
Florian Richoux
quelle
7

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.
Mantosh Kumar
quelle
2
Es gibt keine Garantie dafür, dass tmpes 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 wahrscheinlich tmpunverändert bleiben.)
Casey
6
  1. 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 enumskann das Problem jedoch behoben werden. scoped enumwerden als var deklariert enum 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 enumssind stärker typisiert. Die Enumeratoren von unscoped enumskonvertieren jedoch implizit in andere Typen

      enum 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 enumswird 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 scopedund unscopedAufzählungen unterstützen die Angabe des zugrunde liegenden Typs. Der zugrunde liegende Standardtyp für scoped enumsist int. Unscoped enumshaben keinen zugrunde liegenden Standardtyp.

  2. Verwenden der Parallelitäts-API

    • Bevorzugen Sie aufgabenbasiert gegenüber threadbasiert

      Wenn Sie eine Funktion doAsyncWorkasynchron ausführen möchten , haben Sie zwei grundlegende Möglichkeiten. Einer ist threadbasiert

      int doAsyncWork();
      std::thread t(doAsyncWork);
      

      Der andere ist aufgabenbasiert .

      auto fut = std::async(doAsyncWork);
      

      Natürlich können wir den Rückgabewert erhalten doAsyncWorkdurch taskbasierte leichter als faden basiert . Mit dem task-basedAnsatz ist es einfach, weil die Zukunft, die von zurückgegeben std::asyncwird, die get-Funktion bietet. Die getFunktion ist noch wichtiger, wenn doAsyncWorkeine Ausnahme ausgegeben wird, da auch getder Zugriff darauf möglich ist.

    • Thread-basedfordert eine manuelle Verwaltung der Thread-Erschöpfung, Überbelegung, des Lastausgleichs und der Anpassung an neue Plattformen. Aber Task-basedüber std::asyncden 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

zangw
quelle
Vielleicht möchten Sie die anderen Vorteile von Enums mit Gültigkeitsbereich erwähnen. Auch eine Diskussion darüber, welche Alternativen vor c ++ 11 zur Parallelität existierten, wäre hilfreich.
Shafik Yaghmour
5

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 handelt

Beispiel:

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).

Vivian Miranda
quelle
2
Bewerten Sie Fibonacci-Zahlen niemals so.
Konstruktor
1
Ich weiß nicht warum, besonders für F_N mit N nicht sehr groß (Für großes N gibt es wahrscheinlich eine optimierte Formel). Aber hier ist dies nur ein Beweis für das Prinzip, was man mit constexpr machen kann. Ich habe bereits Leute gesehen, die sich für constexpr-Versionen von pow mit kleiner Ganzzahl, log und sqrt einsetzen, mit einigen Einschränkungen. Wenn Sie zur Kompilierungszeit eine Funktion ausführen können, die in einer inneren Schleife aufgerufen wird, ist dies eine gute Optimierung.
Vivian Miranda
4

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 suchendelete .

Es sollte auch keinen Grund geben, dies zu verwenden new. Ersetzen Sie alle newdurch make_sharedoder make_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 __cplusplusVersion zu suchen ( make_uniqueist in C ++ 14 verfügbar).

Die Verwendung von make_uniqueund make_sharedist sehr wichtig, um Ihre Code-Ausnahme sicher zu machen.

sbabbi
quelle
Die direkte Zuordnung zu einem Smart Pointer ist genauso sicher wie die Verwendung dieser Maker-Funktionen. Wenn der Smart-Pointer-Konstruktor auslöst, wird das erstellte Objekt gelöscht. Make_shared kann jedoch mit einem benutzerdefinierten Allokator + Deallocator optimiert werden, sodass die Buchhaltung für den gemeinsam genutzten Zeiger dem Objekt vorangestellt wird.
Deduplikator
Die direkte Zuordnung zu einem Smart Pointer ist genauso sicher wie die Verwendung dieser Maker-Funktionen. Dies ist nicht der Fall, wenn Sie den Smart Pointer als Argument an eine andere Funktion übergeben. Siehe dies
sbabbi
Sie meinen, wenn Sie den smart_pointer für die Konstruktion als Argument an eine Funktion übergeben und mindestens ein weiteres Argument übergeben, dessen Übergabe / Konstruktion eine Ausnahme auslösen kann, richtig? Es sieht so aus, als ob unsere beiden Kommentare (inverse) Qualifikationsmerkmale benötigen.
Deduplikator
3

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.

opetroch
quelle