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ź Przez R Sahu był moim zaakceptowanym, ponieważ kombinacja funkcji, które proponuje, oddaje duch refaktoryzacji : uczynienie kodu jaśniejszym, czystszym, prostszym i eleganckim.
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_;
};
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.
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;
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)
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
- zmiana
std::map
nastd::unordered_map
istd::set
nastd::unordered_set
tam, gdzie kolejność elementów kontenera jest nieistotna, znacznie zwiększa wydajność. - używanie
std::map::at
zamiast wstawiania składni nawiasów kwadratowych, jeśli chcesz uniknąć mimowolnych wstawek. - użyj szablonów aliasów, gdy chcesz
typedef
szablonów. - użycie list inicjalizacyjnych zamiast pętli for do inicjalizacji kontenerów STL.
- Zastąp tablice c o stałym rozmiarze za pomocą STD:: array.
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;
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.
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).
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.
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
-
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 varenum 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 typesenum 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 iunscoped
enums wspierają specyfikację podstawowego typu. Domyślnym typem bazowym dlascoped enums
jestint
.Unscoped enums
Nie ma domyślnego typu bazowego.
-
-
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ątkachint 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ściemtask-based
jest to łatwe, ponieważ przyszłość zwrócona zstd::async
oferuje funkcję get. Funkcjaget
jest jeszcze ważniejsza, jeślidoAsyncWork
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. AleTask-based
poprzezstd::async
z domyślną Polityką uruchamiania nie ma żadnej z tych wad.
Oto kilka linków:
Programowanie C/C++ Abstrakcje dla równoległości i współbieżności
-
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.
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