Рефакторинг в 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 мой приняли, потому что сочетание из функций он предлагает захватывает дух рефакторинг : делая код яснее и чище, проще и элегантнее.

787   11  

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 самый досадный разбор (остальные причины, по которым новый способ превосходит, объясняются в связанной статье Херба Саттера)

  1. изменение std::map до std::unordered_map и std::set to std::unordered_set там, где когда-либо порядок элементов контейнера не имеет значения, значительно повышает производительность.
  2. используя std::map::at вместо использования вставки синтаксиса квадратных скобок, когда вы хотите избежать непроизвольных вставок.
  3. используйте шаблоны псевдонимов, когда вы хотите typedef шаблоны.
  4. использование списков инициализации вместо циклов для инициализации контейнеров STL.
  5. заменить фиксированные размер 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 - это действительно важно, чтобы сделать ваше исключение кода безопасным.

  1. предпочитаю ограниченных перечислений в неограниченных перечислений

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

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

      однако, в C++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 не имеют базового типа по умолчанию.

  2. использование 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/C++ Программирование абстракций для параллелизма и параллелизма

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

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

Comments

    Ничего не найдено.