Refaktoryzacja w C++ 11

Biorąc pod uwagę nowy zestaw narzędzi dostarczany przez c++ wielu programistów, dążących do uproszczenia kodu, ekspresji, wydajności, przegląda swój stary kod i wprowadza poprawki (niektóre bezsensowne, niektóre udane), aby osiągnąć swoje cele. Starając się nie tracić zbyt wiele czasu na takie prace i po prostu wprowadzać nieinwazyjne i samodzielne zmiany, jakie są najlepsze praktyki?

Wykreślę oczywistość:

  • Użyj auto do uruchamiania pętli opartych na iteratorze :

    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);
    
  • Użyj tie do wielu zadań, które tworzą wiersze kodu w stylu C (Jak przypisać wiele wartości do struktury jednocześnie? )

    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);
    
  • Aby klasa nie była dziedziczna, po prostu zadeklaruj ją jako "ostateczną" i usuń kod, który osiągnął takie zachowanie http://www.parashift.com/c++-faq/final-classes.html

  • Użyj słowa kluczowego delete, aby jawnie ukryć konstruktory/destruktory zamiast deklarowanie ich jako prywatnych (np. kod do tworzenia obiektów opartych na stercie, obiektów nie do skopiowania itp.)

  • Zamieniaj trywialne funktory stworzone tylko po to, aby ułatwić wykonanie pojedynczego algorytmu STL w lambda funkcje (oprócz zmniejszenia bałaganu kodu będziesz miał zagwarantowane wywołania inlined)

  • Uprość zawijanie RAII obiektu za pomocą inteligentnego wskaźnika

  • Pozbądź się bind1st, bind2nd i po prostu użyj bind

  • Zastąp ręcznie napisany kod dla cech typu (Is_ptr_but_dont_call_for_const_ptrs i takie:)) standardowym kodem dostarczonym przez

  • Stop including boost headers for functionallity now implied in STL (BOOST_STATIC_ASSERT vs static_assert)

  • Nie jest to jednak możliwe, ponieważ nie jest to możliwe w przypadku klas, które nie są w pełni kompatybilne z klasami.]}

  • Użyj nullptr gdzie możliwe zamiast makra NULL i pozbądź się kodu, który wypełniał kontenery wskaźników z 0-ami rzucanymi na typ obiektu

    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);
    
  • Wyczyść składnię dostępu do danych wektorowych

    std::vector<int> vec;
    &vec[0];    // access data as a C-style array
    vec.data(); // new way of saying the above
    
  • Zastąp throw() przez noexcept (oprócz unikania przestarzałej specyfikacji wyjątku otrzymujesz pewne korzyści z szybkości 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
    
  • Zastąp kod, w którym można było wcisnąć tempory w kontenerze i mieć nadzieję, że optymalizator usunie kopię za pomocą funkcji "emplace" , jeśli jest dostępna, w celu perfekcyjnego przekazania argumentu i skonstruowania bezpośrednio obiektu do kontenera bez tymczasowego w ogóle.

    vecOfPoints.push_back(Point(x,y,z)); // so '03
    vecOfPoints.emplace_back(x, y, z);   // no copy or move operations performed
    

UPDATE

Odpowiedź Shafika Yaghmoura [22] została słusznie nagrodzona nagrodą bounty za największą akceptację ze strony publiczności.

Odpowiedź Przez R Sahu był moim zaakceptowanym, ponieważ kombinacja funkcji, które proponuje, oddaje duch refaktoryzacji : uczynienie kodu jaśniejszym, czystszym, prostszym i eleganckim.

Author: Community, 2014-02-02

11 answers

Dodałbym do listy delegujące konstruktory i IN-class Member initializers.

Uproszczenie za pomocą konstruktorów delegujących i inicjalizacji w klasie

Z 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_;
};

Z 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_;
};
 16
Author: R Sahu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-02 21:26:30

1. Zastępowanie rand

Jedną z większych korzyści w C++11 jest zastąpienie użycia rand() wszystkimi opcjami dostępnymi w nagłówku losowym . Zastąpienie rand() w wielu przypadkach powinno być proste.

Stephan T. Lavavej prawdopodobnie uczynił ten punkt najsilniejszym ze swojej prezentacji rand () uważanej za szkodliwą . Przykłady pokazują równomierny rozkład liczb całkowitych z [0,10] za pomocą 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 ;
}

I używając 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 ;
}
W związku z tym należy przejść z std::random_shuffle do std::shuffle, co wynika z wysiłku Deprecate rand and Friends. To zostało niedawno omówione w SO pytanie dlaczego metody STD::shuffle są przestarzałe w C++14?.

Zauważ, że dystrybucje nie są gwarantowane spójne na platformach .

2. Using std::to_string zamiast std:: ostringstream lub sprintf

C++11 dostarcza std:: to_string, który może być użyty do konwersji liczb na std::string, który tworzy zawartość jako odpowiednik std::sprintf. Najprawdopodobniej byłoby to używane zamiast std:: ostringstream lub snprintf. Jest to bardziej wygodne, prawdopodobnie nie ma dużej różnicy w wydajności i możemy zobaczyć z Szybka konwersja liczby całkowitej do ciągu znaków w C++ artykuł prawdopodobnie są znacznie szybsze alternatywy dostępne, jeśli wydajność jest głównym problemem:

#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. Używanie constexpr zamiast szablonowego meta-programowania

Jeśli masz do czynienia z literałami, mogą się zdarzyć przypadki, w których użycie funkcji constexpr nad meta-programowaniem szablonów może wytworzyć kod, który jest bardziej przejrzysty i być może szybciej kompiluje się. Artykuł chcesz prędkości? Użyj meta-programowania constexpr! przedstawia przykład wyznaczania liczby pierwszej użycie szablonu meta-programowanie:

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 };
};

I korzystanie z funkcji constexpr:

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);
}

Wersja constexpr jest znacznie krótsza, łatwiejsza do zrozumienia i najwyraźniej działa znacznie lepiej niż implementacja meta-programowania szablonów.

4. Używanie inicjalizacji członka klasy do podania wartości domyślnych

Jak zostało ostatnio omówione w czy nowa funkcja inicjalizacji członków C++11 w deklaracji sprawiła, że listy inicjalizacji stały się przestarzałe? Klasa inicjalizacja elementu może być użyta do podania wartości domyślnych i może uprościć przypadki, w których klasa ma wiele konstruktorów.

Bjarne Stroustrup podaje dobry przykład w C++11 FAQ, mówi:

To oszczędza trochę pisania, ale realne korzyści przychodzą w klasach z wieloma konstruktorami. Często wszystkie konstruktory używają wspólnego inicjalizatora dla elementu:

I podaje przykład członków, którzy mają wspólne inicjalizacja:

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
};

I mówi:

Fakt, że hash_algorithm i s mają jedną wartość domyślną, jest zagubiony w bałaganie kodu i może łatwo stać się problemem podczas konserwacji. Zamiast tego możemy uwzględnić inicjalizację elementów danych:

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
};

Zauważ, że w C++11 Klasa używająca inicjalizatorów klasy jest nie jest już zbiorczym , chociaż ograniczenie to jest usuwane w C++14.

5. Użyj liczby całkowitej o stałej szerokości typy z cstdint zamiast ręcznie zwijanych typedefs

Ponieważ standard C++11 używa C99 jako odniesienia normatywnego, otrzymujemy typy liczb całkowitych o stałej szerokości. Na przykład:

int8_t
int16_t 
int32_t 
int64_t 
intptr_t

Chociaż kilka z nich jest opcjonalnych, dla dokładnych typów całkowitych szerokości stosuje się następujące z sekcji C99 7.18.1.1:

Te typy są opcjonalne. Jednakże, jeśli implementacja udostępnia typy całkowite o szerokości 8, 16, 32 lub 64 bity, bez bitów wypełniających oraz (dla podpisanych typów), że mieć reprezentację dopełniacza dwójki, definiuje ona odpowiednie nazwy typedef.

 27
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:15

For-każda składnia:

std::vector<int> container;

for (auto const & i : container)
  std::cout << i << std::endl;
 11
Author: voodooattack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-02 20:35:28

Użyj jednolitej składni inicjalizacji dla inicjalizacja zmiennej

widget w(x); // old
widget w{x}; // new

Aby uniknąć problemów, takich jak najbardziej irytujący parse c++ (resztę powodów, dla których nowa droga jest lepsza, wyjaśniono w linkowanym artykule autorstwa Herba Suttera)

 7
Author: Community,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:30:55
  1. zmiana std::map na std::unordered_map i std::set na std::unordered_set tam, gdzie kolejność elementów kontenera jest nieistotna, znacznie zwiększa wydajność.
  2. używanie std::map::at zamiast wstawiania składni nawiasów kwadratowych, jeśli chcesz uniknąć mimowolnych wstawek.
  3. użyj szablonów aliasów, gdy chcesz typedef szablonów.
  4. użycie list inicjalizacyjnych zamiast pętli for do inicjalizacji kontenerów STL.
  5. Zastąp tablice c o stałym rozmiarze za pomocą STD:: array.
 7
Author: 101010,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-06 00:14:15

Ten wpis na blogu proponuje regułę zera jeśli wszystkie własności do klasy postępują zgodnie z zasadą RAII, pozwalającą pozbyć się reguły trzech/czterech/pięciu W C++11.

Jednak Scott Meyers pokazuje tutaj, że nie pisanie jawnie destruktora, konstruktorów kopiowania/przenoszenia i operatorów przydziału może wywołać subtelne problemy, jeśli lekko zmienisz kod (powiedzmy, do debugowania). Następnie zaleca jawne zadeklarowanie default (C++11 feature) tych funkcji:

~MyClass()                           = default;
MyClass( const MyClass& )            = default;
MyClass( MyClass&& )                 = default;
MyClass& operator=( const MyClass& ) = default;
MyClass& operator=( MyClass&& )      = default;
 6
Author: Florian Richoux,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-31 07:58:22

Feature: std:: move

"wyraźna różnica między kopiowaniem a przenoszeniem zasobów"

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.
 6
Author: Mantosh Kumar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-31 15:49:00

Optymalizuj proste funkcje matematyczne za pomocą constexpr, zwłaszcza jeśli są wywoływane wewnątrz pętli wewnętrznych. Pozwala to kompilatorowi obliczyć je podczas kompilacji, oszczędzając czas

Przykład

constexpr int fibonacci(int i) {
    return i==0 ? 0 : (i==1 ? 1 : fibonacci(i-1) + fibonacci(i-2));
}

Innym przykładem jest użycie std::enable_if do ograniczenia dozwolonych typów parametrów szablonu w danej funkcji/klasie szablonu. To sprawi, że Twój kod będzie bezpieczniejszy (w przypadku, gdy nie używasz SFINAE do ograniczenia możliwych argumentów szablonu w starym kodzie), gdy przyjmij jakąś właściwość dotyczącą typów szablonów i jest to tylko jedna dodatkowa linia kodu

Przykład:

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: Jeśli masz małą tablicę, której rozmiar jest znany podczas kompilacji i chcesz uniknąć narzutu stosu w std:: vector( czyli: chcesz mieć tablicę na stosie), jedynym wyborem w C++03 było użycie tablic w stylu C. Zmień to na std::array. Jest to prosta zmiana, która zapewnia wiele funkcjonalności obecnych w std:: vector + stack alokacja (znacznie szybciej niż alokacja sterty, jak powiedziałem wcześniej).

 5
Author: Vivian Miranda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-03 07:28:49

Użyj inteligentnych wskaźników. Zauważ, że nadal istnieją dobre powody, aby mieć nagie wskaźniki w niektórych przypadkach, najlepszym sposobem, aby sprawdzić, czy wskaźnik powinien być inteligentny, jest szukanie zastosowań delete na nim.

Nie powinno być też powodu do używania new. Zastąpić każdy new przez make_shared lub make_unique.

Niestety make_unique nie udało się w standardzie C++11, najlepszym rozwiązaniem IMO jest zaimplementowanie go samemu (Zobacz poprzedni link ), i wrzuć kilka makr do sprawdzenia wersja __cplusplus (make_unique jest dostępna w C++14).

Użycie make_unique i make_shared jest naprawdę ważne, aby twój wyjątek kodu był bezpieczny.

 3
Author: sbabbi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:54
  1. Preferuj enumery z zakresami od enumerów bez zakresów

    • W C++98 enums, nie ma zasięgu dla enums, jak Poniższy fragment kodu. Nazwy takich enumeratorów należą do zakresu zawierającego enum, czyli nic innego w tym zakresie nie może mieć tej samej nazwy.

      enum Color{ blue, green, yellow };
      bool blue = false;    // error: 'blue' redefinition
      

      Jednak w C++11, scoped enums może rozwiązać ten problem. scoped enum są zadeklarowane var 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
      
    • Liczniki {[11] } są silniej wpisane. Ale, enumerators of unscoped enums implicite convert to other types

      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 !!
      }
      

      Jednak scoped enums w tym przypadku nie powiedzie się.

      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 !!
      }
      

      Fix it through static_cast

      if (static_cast<double>(c) < 10.1) {
         auto vec = getVector(static_cast<std::size_t>(c));
      } 
      
    • unscoped enums może być zadeklarowany terminowo.

      enum Color;          // error!!
      enum class Color;    // fine
      
    • Zarówno scoped, jak i unscoped enums wspierają specyfikację podstawowego typu. Domyślnym typem bazowym dla scoped enums jest int. Unscoped enums Nie ma domyślnego typu bazowego.

  2. Używanie Współbieżności API

    • Preferuj oparte na zadaniach od wątków

      Jeśli chcesz uruchomić funkcję doAsyncWork asynchronicznie, masz dwie podstawowe opcje. Jeden jest oparty na wątkach

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

      Drugi jest oparty na zadaniach .

      auto fut = std::async(doAsyncWork);
      

      Oczywiście, możemy uzyskać wartość zwracaną doAsyncWork poprzez bazujące na zadaniach łatwiej niż bazujące na wątkach. Z podejściem task-based jest to łatwe, ponieważ przyszłość zwrócona z std::async oferuje funkcję get. Funkcja get jest jeszcze ważniejsza, jeśli doAsyncWork emituje wyjątek, ponieważ get zapewnia również dostęp do niego.

    • Thread-based wymaga ręcznego zarządzania wyczerpaniem wątków, nadsubskrypcją, równoważeniem obciążenia i adaptacją do nowych platform. Ale Task-based poprzez std::async z domyślną Polityką uruchamiania nie ma żadnej z tych wad.

    Oto kilka linków:

    Współbieżność W C++

    Programowanie C/C++ Abstrakcje dla równoległości i współbieżności

 3
Author: zangw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-14 07:47:16

Użycie nadpisanie słowa kluczowego

Oznacz funkcje wirtualne w klasach pochodnych jako override(jeśli rzeczywiście override oczywiście). Może to chronić przed wprowadzaniem błędów w przyszłości, np. poprzez zmianę podpisu funkcji wirtualnej w klasie bazowej i zapomnienie o odpowiedniej zmianie podpisu we wszystkich klasach pochodnych.

 1
Author: opetroch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-08 09:26:53