Подтвердить что ты не робот

Рефакторинг с С++ 11

Учитывая новый набор инструментов, предоставляемый С++, многие программисты, стремясь упростить код, выразительность, эффективность, проскальзывать через свой старый код и сделать трюки (некоторые бессмысленные, некоторые успешные) для достижения своих целей. Не пытаясь потерять слишком много времени на таких трудах и просто сделать неинтрузивные и самодостаточные изменения, каковы лучшие практики?

Позвольте мне вычеркнуть очевидное:

  • Используйте авто для запуска циклов на основе итератора:

    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 в функциях lambda (помимо уменьшения загромождения кода у вас будут гарантированные встроенные вызовы)

  • Упростите обтекание RAII объекта с помощью умного указателя

  • Избавьтесь от bind1st, bind2nd и просто используйте bind

  • Заменить написанный вручную код для типов типов (Is_ptr_but_dont_call_for_const_ptrs < > и таких:)) со стандартным кодом, предоставленным type_traits >

  • Остановить включение форвардных заголовков для функциональности, теперь вложенных в STL (BOOST_STATIC_ASSERT vs static_assert)

  • Предоставить семантику перемещения в классы (хотя это не будет считаться грязным/быстрым/легким изменением)

  • Используйте nullptr, если это возможно, вместо макроса NULL и избавитесь от кода, который заполняет контейнеры указателей с 0, отлитыми от типа объекта

    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
    

UPDATE

Ответ Шафика Яхмура был по праву награжден за щедрость за то, что он получил наибольшее признание со стороны аудитории.

Ответ R Sahu был моим принятым, потому что комбинация функций, которые он предлагает, захватывает дух рефакторинга: сделать код более понятным и чистым, более простым и элегантным.

4b9b3361

Ответ 1

Я бы добавил делегирование конструкторов и инициализаторов членов класса в список.

Упрощение с помощью делегирования конструкторов и инициализации внутри класса

С С++ 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_;
};

С С++ 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_;
};

Ответ 2

1. Замена rand

Один из больших выигрышей в С++ 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, который исходит из усилий Отменить rand и друзей. Это недавно было рассмотрено в вопросе SO Почему методы std:: shuffle устарели в С++ 14?.

Обратите внимание, что дистрибутивы не гарантируются для разных платформ.

2. Использование std:: to_string вместо std:: ostringstream или sprintf

С++ 11 предоставляет std:: to_string, который можно использовать для преобразования чисел в std::string, это создаст контент как эквивалентный std:: sprintf. Скорее всего, это будет использоваться вместо std:: ostringstream или snprintf. Это более удобно, вероятно, не так много разницы в производительности, и мы можем видеть из Быстрое целое число для преобразования строк в С++. вероятно, гораздо более быстрые альтернативы, если производительность является основной проблемой:

#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 над метапрограммой шаблона может привести к более четкому и, возможно, компилятору кода. Статья Хотите скорость? Использование метапрограммирования 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. Использование инициализации члена класса для предоставления значений по умолчанию

Как недавно было рассмотрено в Была ли введена новая функция инициализации члена С++ 11 в объявлении, сделанной инициализацией списков устаревших?, чтобы инициализировать член класса, чтобы предоставлять значения по умолчанию и может упростить случаи, когда класс имеет несколько конструкторов.

Bjarne Stroustrup является хорошим примером в FAQ на С++ 11, он говорит:

Это экономит немного ввода, но реальные преимущества приходят в классы с несколькими конструкторами. Часто для всех конструкторов используется обычный инициализатор для члена:

и предоставляет пример элементов, которые имеют общий инициализатор:

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

Обратите внимание, что в С++ 11 класс, использующий в инициализаторах члена класса, больше не является агрегатом, хотя это ограничение удалено в С++ 14.

5. Используйте целые типы фиксированной ширины из cstdint вместо ручных typedefs

Так как стандарт С++ 11 использует C99 в качестве нормативной ссылки, мы получаем фиксированные ширины целочисленных типов. Например:

int8_t
int16_t 
int32_t 
int64_t 
intptr_t

Хотя некоторые из них необязательны, для точных целых чисел ширины применяется следующее из раздела C99 7.18.1.1:

Эти типы являются необязательными. Однако, если реализация обеспечивает целочисленные типы с шириной 8, 16, 32 или 64 бита, без битов заполнения и (для подписанных типов), которые имеют представление двойного дополнения, оно должно определить соответствующие имена typedef.

Ответ 3

Для каждого синтаксиса:

std::vector<int> container;

for (auto const & i : container)
  std::cout << i << std::endl;

Ответ 4

Используйте равномерный синтаксис инициализации для инициализация переменных

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

чтобы избежать таких проблем, как С++ наиболее неприятный синтаксический анализ (остальные причины, по которым новый способ превосходит, объясняются в связанной статье Herb Sutter)

Ответ 5

  • Изменение std::map на std::unordered_map и std::set на std::unordered_set, где когда-либо порядок элементов контейнера не имеет значения, значительно повышает производительность.
  • Использование std::map::at вместо использования синтаксиса с квадратной скобкой, когда вы хотите избежать непроизвольных вставок.
  • Используйте шаблоны псевдонимов, если хотите typedef шаблоны.
  • Использование списков инициализации вместо циклов для инициализации контейнеров STL.
  • Замените массивы с фиксированным размером C на std:: array.

Ответ 6

Функция: 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.

Ответ 7

Этот пост в блоге предлагает Rule of Zero, если все владельцы в классе следуют принципу RAII, позволяя избавиться от правила трех /Four/Five в С++ 11.

Однако Скотт Мейерс показывает здесь, что явно не пишет деструктор, конструкторы копирования/перемещения и назначения могут вызвать тонкие проблемы, если вы слегка измените ваш код (скажем, для отладки). Затем он рекомендует явно объявить default (функция С++ 11) следующие функции:

~MyClass()                           = default;
MyClass( const MyClass& )            = default;
MyClass( MyClass&& )                 = default;
MyClass& operator=( const MyClass& ) = default;
MyClass& operator=( MyClass&& )      = default;

Ответ 8

Оптимизируйте простые математические функции с помощью 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
}

Обновление 1: Если у вас есть небольшой массив, размер которого известен во время компиляции, и вы хотите избежать накладных расходов на распределение кучи в std::vector (что означает: вы хотите, чтобы массив в стеке), вы только выбор в С++ 03 состоял в использовании массивов c-style. Измените это на std::array. Это простое изменение, которое предоставляет вам много функционально присутствующих в распределении стека std::vector + (намного быстрее, чем распределение кучи, как я сказал ранее).

Ответ 9

Используйте интеллектуальные указатели. Обратите внимание, что в некоторых случаях по-прежнему есть веские причины иметь голой указатель, лучший способ проверить, должен ли указатель быть умным, - искать в нем delete.

Не должно быть причин использовать new. Замените каждый new на make_shared или make_unique.

К сожалению, make_unique не сделал это в стандарте С++ 11, лучшим решением IMO является его реализация (см. предыдущую ссылку) и поместите некоторые макросы для проверки версии __cplusplus (make_unique доступен на С++ 14).

Использование make_unique и make_shared действительно важно для обеспечения безопасности кода.

Ответ 10

  • Предпочитают перечисления с привязкой к незанятым перечислениям

    • В С++ 98 перечислениях нет перечислений для перечислений, таких как следующий фрагмент кода. Имена таких счетчиков относятся к области, содержащей перечисление, а именно ничто в этой области не может иметь одно и то же имя.

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

      Однако, в С++ 11, scoped enums может исправить эту проблему. scoped enum объявляются 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
      
    • Перечислители 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_cast

      if (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 не имеют базового типа по умолчанию.

  • Использование Concurrency 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 со стандартной политикой запуска не страдает ни одним из этих недостатков.

    Вот несколько ссылок:

    Concurrency В С++

    C/С++ Программирование Абстракции для Parallelism и Concurrency

Ответ 11

Использование ключевого слова переопределить

Отметьте виртуальные функции в производных классах как переопределенные (если они действительно переопределяют, конечно). Это может защитить от введения ошибок в будущем, например. путем изменения сигнатуры виртуальной функции в базовом классе и забывания изменить подпись во всех производных классах соответственно.