Рефакторинг в C++ 11
учитывая новый набор инструментов, предоставленный c++ множеством программистов, направленных на упрощение кода, выразительность, эффективность, просматривайте свой старый код и делайте настройки (некоторые бессмысленные, некоторые успешные) для достижения своих целей. Пытаясь не терять слишком много времени на такие труды и просто делать ненавязчивые и самодостаточные изменения, каковы лучшие практики?
позвольте мне вычеркнуть очевидное:
использовать авто запустить циклы на основе итератора:
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);
использовать галстук для нескольких назначений, которые просто создают строки кода в стиле C (как назначить несколько значений в структуру сразу?)
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);
чтобы сделать класс не наследуемым, просто объявите его как "окончательный" и удалите код, который достиг такого поведения http://www.parashift.com/c++-faq/final-classes.html
использовать ключевое слово delete явно скрывает конструкторы / деструкторы вместо объявления их частными (например, код для создания объектов на основе кучи, не копируемых объектов и т. д.)
включите тривиальные функторы, созданные только для облегчения выполнения одного алгоритма STL в лямда - функции (помимо уменьшения загромождения кода Вы будете иметь гарантированные встроенные вызовы)
упростить RAII обертывание объекта, просто используя смарт-указатель
избавиться от bind1st, bind2nd и просто использовать связать
замените рукописный код для признаков типа (Is_ptr_but_dont_call_for_const_ptrs и таких:)) стандартным кодом, предоставленным
стоп, в том числе увеличить заголовки на функциональность, теперь implented в STL (BOOST_STATIC_ASSERT против static_assert)
обеспечить семантику перемещения в классы (хотя это не будет квалифицироваться как грязное/быстрое/легкое изменение)
использовать nullptr где это возможно вместо нулевого макроса и избавиться от кода, который заполнял контейнеры указателей с 0's casted to object type
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);
очистить синтаксис доступа к векторным данным
std::vector<int> vec;
&vec[0]; // access data as a C-style array
vec.data(); // new way of saying the above
заменить throw () на noexcept (помимо избежания устаревшего описания исключений вы получаете некоторые преимущества скорости 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
замените код, в котором вы бы нажали tempory в контейнере и надеялись, что оптимизатор удалит копию, с помощью "emplace" функция, где это возможно, для того, чтобы идеально переслать аргумент и построить непосредственно объект в контейнер без временного вообще.
vecOfPoints.push_back(Point(x,y,z)); // so '03
vecOfPoints.emplace_back(x, y, z); // no copy or move operations performed
обновление
ответ Шафика Ягмура был по праву награжден премией за то, что он получил наибольшее признание аудитории.
ответ от R Sahu мой приняли, потому что сочетание из функций он предлагает захватывает дух рефакторинг : делая код яснее и чище, проще и элегантнее.
11 ответов:
Я бы добавил делегирование конструкторов и инициализаторов членов класса в список.
упрощение с помощью делегирования конструкторов и инициализации в классе
С 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_; };С 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_; };
1. Замена Рэнд
одним из больших достижений в C++11 должна быть замена использования
rand()со всеми опциями, доступными в случайный заголовок. Заменаrand()во многих случаях должна быть прямой.Стефан Т. Лававей вероятно, этот момент был самым сильным с его презентацией rand () считается вредным. Примеры показывают равномерное целочисленное распределение от
[0,10]используя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 ; }и с помощью 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 ; }вместе с этим следует двигаться от std:: random_shuffle до std:: shuffle который выходит из усилия, чтобы осуждать Рэнд и друзей. Это было недавно рассмотрено в SO question почему методы std:: shuffle устарели в C++14?.
обратите внимание, что дистрибутивы не гарантированно будет согласовано между платформами.
2. Использование std:: to_string вместо std:: ostringstream или sprintf
C++11 предоставляет std:: to_string, который может быть использован для преобразования чисел к std:: string он будет производить содержимое как эквивалент std:: sprintf. Скорее всего это будет использоваться вместо std:: ostringstream или
snprintf. Это еще не все из удобства, вероятно, нет большой разницы в производительности, и мы можем видеть из быстрое преобразование целого числа в строку в C++ статья есть, вероятно, гораздо более быстрые альтернативы доступны, если производительность является основной проблемой:#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. Использование constexpr вместо шаблона мета-программирования
если вы имеете дело с литералами, могут быть случаи, когда использование функций constexpr над шаблоном мета-программирования может создайте код, который более понятен и, возможно, компилируется быстрее. В статье нужна скорость? Использовать мета-Программирование пользователем! приведен пример определения простых чисел с помощью шаблона мета-программирования:
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 }; };и используя функции 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); }версия constexpr намного короче, проще для понимания и, по-видимому, работает намного лучше, чем реализация метапрограммирования шаблона.
4. Использование класса инициализация элемента для предоставления значений по умолчанию
как было недавно описано в новая функция инициализации членов C++11 при объявлении сделала списки инициализации устаревшими? инициализация члена класса может использоваться для предоставления значений по умолчанию и может упростить случаи, когда класс имеет несколько конструкторов.
Бьярн Страуструп дает хороший пример в C++11 FAQ, он говорит:
это экономит немного набрав, но реальные преимущества приходят в классы с несколькими конструкторами. Часто все конструкторы используют общий инициализатор для элемента:
и предоставляет пример членов, которые имеют общий инициализатор:
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 };и говорит:
тот факт, что hash_algorithm и s имеют одно значение по умолчанию, теряется в беспорядке кода и может легко стать проблемой во время обслуживания. Вместо этого мы можем разложить на множители инициализация элементов данных:
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 };обратите внимание, что в C++11 класс, использующий инициализаторы членов класса,больше не агрегатная хотя это ограничение снимается в C++14.
5. Используйте целочисленные типы фиксированной ширины из cstdint вместо руки свернутые определения типов
поскольку стандарт C++11 использует C99 в качестве нормативной ссылки, мы получаем целочисленные типы фиксированной ширины, так же. Для пример:
int8_t int16_t int32_t int64_t intptr_tхотя некоторые из них необязательны, для точной ширины целочисленных типов следующее из раздела C99
7.18.1.1применяется:эти типы являются необязательными. Однако, если реализация предоставляет целочисленные типы с шириной 8, 16, 32 или 64 бита, без битов заполнения и (для подписанных типов), что имейте представление дополнения 2, оно определит соответствующие имена typedef.
для каждого синтаксис:
std::vector<int> container; for (auto const & i : container) std::cout << i << std::endl;
использовать инициализации синтаксис ибо инициализация переменной
widget w(x); // old widget w{x}; // newчтобы избежать таких проблем, как c++ ' S самый досадный разбор (остальные причины, по которым новый способ превосходит, объясняются в связанной статье Херба Саттера)
- изменение
std::mapдоstd::unordered_mapиstd::settostd::unordered_setтам, где когда-либо порядок элементов контейнера не имеет значения, значительно повышает производительность.- используя
std::map::atвместо использования вставки синтаксиса квадратных скобок, когда вы хотите избежать непроизвольных вставок.- используйте шаблоны псевдонимов, когда вы хотите
typedefшаблоны.- использование списков инициализации вместо циклов для инициализации контейнеров STL.
- заменить фиксированные размер c массивов с std:: array.
это сообщение в блоге предлагает правило ноль если все владения в классе следуют принципу RAII, позволяющему избавиться от Правила три / четыре / пять в C++11.
однако, Скотт Мейерс показывает здесь что не писать явно деструктор, копировать/переместить конструкторы и операторы присваивания могут вызвать тонкие проблемы, если вы немного измените свой код (например, для отладки). Затем он рекомендует явно объявить по умолчанию (C++11 особенность) эти функции:
~MyClass() = default; MyClass( const MyClass& ) = default; MyClass( MyClass&& ) = default; MyClass& operator=( const MyClass& ) = default; MyClass& operator=( MyClass&& ) = default;
характеристика: std:: move
"выразите четкое различие между копированием и перемещением ресурсов"
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.
оптимизируйте простые математические функции с помощью constexpr, особенно если они вызываются внутри внутренних циклов. Это позволит компилятору вычислять их при компиляции, экономя ваше время
пример
constexpr int fibonacci(int i) { return i==0 ? 0 : (i==1 ? 1 : fibonacci(i-1) + fibonacci(i-2)); }другой пример-использовать
std::enable_ifчтобы ограничить допустимые типы параметров шаблона в определенной функции/классе шаблона. Это сделает ваш код более безопасным (в случае, если вы не используете SFINAE для ограничения возможных аргументов шаблона в вашем старом коде) когда вы неявно предполагаете некоторое свойство о типах шаблонов, и это всего лишь одна дополнительная строка кодапример:
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: Если у вас есть небольшой массив, размер которого известен во время компиляции, и вы хотите избежать накладных расходов на выделение кучи в std:: vector (что означает: вы хотите, чтобы массив в стеке), вы только выбрали в C++03 использовать массивы c-стиля. Измените это на
std::array. Это простое изменение, которое предоставляет вам много функционально присутствует в std:: vector + Stack allocation (гораздо быстрее, чем выделение кучи, как я уже говорил).
использовать смарт-указатели. Обратите внимание, что в некоторых случаях все еще есть веская причина иметь голые указатели, лучший способ проверить, должен ли указатель быть умным, - это искать использование
deleteна нем.не должно быть никаких причин, чтобы использовать
newлибо. Заменить всеnewСmake_sharedилиmake_unique.к сожалению
make_uniqueне сделал это в стандарте C++11, лучшее решение ИМО-это реализовать его самостоятельно(посмотреть предыдущие ссылка), и поставить некоторые макросы, чтобы проверить__cplusplusверсия (make_uniqueдоступно в C++14).используя
make_uniqueиmake_shared- это действительно важно, чтобы сделать ваше исключение кода безопасным.
предпочитаю ограниченных перечислений в неограниченных перечислений
в C++98 перечисления, нет области для перечисления как в следующем фрагменте кода. Имена таких перечислителей относятся к области, содержащей перечисление, а именно ничто другое в этой области не может иметь такое же имя.
enum Color{ blue, green, yellow }; bool blue = false; // error: 'blue' redefinitionоднако, в C++11,
scoped enumsисправить эту проблему.scoped enumобъявляются 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перечислителей
scope enumsболее строго типизированным. Но, перечислителиunscoped enumsнеявно конвертировать в другие типы,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 !! }scoped enumsбудет не в этом случае.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 !! }исправить это через
static_castif (static_cast<double>(c) < 10.1) { auto vec = getVector(static_cast<std::size_t>(c)); }
unscoped enumsможет быть вперед,-заявил.enum Color; // error!! enum class Color; // fineи
scopedиunscopedперечисления поддерживают спецификацию базового типа. Базовый тип по умолчанию дляscoped enumsиint.Unscoped enumsне имеют базового типа по умолчанию.использование API параллелизма
предпочитают задачи на основе потоков
если вы хотите запустить функцию
doAsyncWorkасинхронно, у вас есть два основных варианта. Один из них нить на основеint doAsyncWork(); std::thread t(doAsyncWork);другой задач.
auto fut = std::async(doAsyncWork);очевидно, что мы можем получить возвращаемое значение
doAsyncWorkчерез задач легче, чем нить на основе. С помощьюtask-basedподход, это легко, потому что будущее вернулся изstd::asyncпредлагает функцию get. Элементgetфункция еще более важна, еслиdoAsyncWorkвыдает исключение, потому чтоgetобеспечивает доступ к этому тоже.
Thread-basedзвонки для руководства управление исчерпанием потока, избыточной подпиской, балансировкой нагрузки и адаптацией к новым платформам. НоTask-basedчерезstd::asyncС политикой запуска по умолчанию не страдает ни от одного из этих недостатков.вот несколько ссылок:
C/C++ Программирование абстракций для параллелизма и параллелизма
использование переопределить ключевое слово
пометить виртуальные функции в производных классах как переопределение (если они действительно переопределяют, конечно). Это может защитить от введения ошибок в будущем, например, путем изменения сигнатуры виртуальной функции в базовом классе и забывания изменить сигнатуру во всех производных классах соответственно.
Comments