简体   繁体   English

使用 C++ 11 进行重构

[英]Refactoring with C++ 11

Given the new toolset provided by c++ lots of programmers, aiming at code simplification, expressiveness, efficiency, skim through their old code and make tweaks (some pointless, some successful) to achieve their goals.鉴于 c++ 提供的新工具集,许多程序员以代码简化、表达性、效率为目标,浏览他们的旧代码并进行调整(有些毫无意义,有些成功)以实现他们的目标。 Whilst trying not to loose too much time on such labors and just make non intrusive and self contained changes, what are the best practices?虽然尽量不要在此类工作上浪费太多时间,而只是进行非侵入性和自包含的更改,但最佳实践是什么?

Let me cross out the obvious :让我划掉明显的:

  • Use auto to run iterator based loops :使用auto运行基于迭代器的循环:

     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);
  • Use tie for multiple assignments that just produce C-style rows of code ( how to assign multiple values into a struct at once? )tie用于仅产生 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);
  • To make a class non inheritable just declare it as "final" and delete the code that achieved such a behavior http://www.parashift.com/c++-faq/final-classes.html要使类不可继承,只需将其声明为“final”并删除实现这种行为的代码http://www.parashift.com/c++-faq/final-classes.html

  • Use the delete keyword to explicitly hide constructors/destructors instead of declaring them private (eg code to create heap based objects, non copyable objects etc)使用 delete 关键字显式隐藏构造函数/析构函数,而不是将它们声明为私有(例如创建基于堆的对象、不可复制对象等的代码)

  • Turn trivial functors created just to facillitate the execution of a single STL algorithm into lambda functions (apart from reducing code cluttering you'll have guaranteed inlined calls)将仅为简化单个 STL 算法的执行而创建的琐碎仿函数转换为lambda函数(除了减少代码混乱之外,您还可以保证内联调用)

  • Simplify RAII wrapping of an object by just using a smart pointer仅使用智能指针简化对象的 RAII 包装

  • Get rid of bind1st, bind2nd and just use bind摆脱bind1st,bind2nd,只使用bind

  • Replace hand written code for type traits (Is_ptr_but_dont_call_for_const_ptrs<> and such :) ) with standard code provided by < type_traits >< type_traits >提供的标准代码替换类型特征的手写代码(Is_ptr_but_dont_call_for_const_ptrs<> 等 :))

  • Stop including boost headers for functionallity now implented in STL (BOOST_STATIC_ASSERT vs static_assert)停止包含现在在 STL 中实现的功能的提升标头(BOOST_STATIC_ASSERT 与 static_assert)

  • Provide move semantics to classes (although this wouldn't qualify as a dirty/quick/easy change)为类提供移动语义(尽管这不符合肮脏/快速/简单的更改)

  • Use nullptr where possible instead of the NULL macro and get rid of the code that filled containers of pointers with 0's casted to object type在可能的情况下使用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);
  • Clear the vector data accessing syntax清除矢量数据访问语法

    std::vector<int> vec; &vec[0]; // access data as a C-style array vec.data(); // new way of saying the above
  • Replace throw() with noexcept (apart from avoiding the deprecated exception specifiation you get some speed benefits http://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler @ 00.29.42)将 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
  • Replace code where you'd push a tempory in a container and hoped that the optimizer would ellide the copy away, with an "emplace" function where available, in order to perfectly forward the argument and construct directly an object into a container without temporary at all.替换您将在容器中推送临时的代码,并希望优化器将副本删除,并在可用的情况下使用“emplace”函数,以便完美地转发参数并将对象直接构造到容器中,而无需临时全部。

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

UPDATE更新

The answer by Shafik Yaghmour was rightfully awarded the bounty for having the greatest acceptance by the audience. Shafik Yaghmour 的回答理所当然地获得了观众最大的接受度。

The answer by R Sahu was my accepted one, because the combination of features it proposes captures the spirit of refactoring : making code clearer and cleaner and simpler and elegant. R Sahu 的回答是我接受的答案,因为它提出的功能组合体现了重构的精神:使代码更清晰、更简洁、更优雅。

1. Replacing rand 1.更换兰特

One of the big gains in C++11 has to be replacing the use of rand() with all the options available in the random header . C++11 的一大收获是使用random header中可用的所有选项替换rand()的使用。 Replacing rand() in many cases should be straight forward.在许多情况下替换rand()应该是直截了当的。

Stephan T. Lavavej probably made this point the strongest with his presentation rand() Considered Harmful . Stephan T. Lavavej的演示文稿rand() 被认为是有害的可能使这一点最为强烈。 The examples show a uniform integer distribution from [0,10] using rand() :这些示例显示了使用rand()[0,10]开始的均匀整数分布:

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

and using std::uniform_int_distrubution :并使用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 ;
}

Along with this should be moving from std::random_shuffle to std::shuffle which comes out of the effort to Deprecate rand and Friends .与此同时,应该从std::random_shuffle 转移std::shuffle ,这是为了弃用 rand 和 Friends的努力。 This was recently covered in the SO question Why are std::shuffle methods being deprecated in C++14?最近在 SO 问题中对此进行了介绍, 为什么在 C++14 中不推荐使用 std::shuffle 方法? . .

Note that the distributions are not guaranteed to be consistent across platforms .请注意,不保证跨平台的分布是一致的

2. Using std::to_string instead of std::ostringstream or sprintf 2. 使用 std::to_string 代替 std::ostringstream 或 sprintf

C++11 provides std::to_string which can be used to convert numerics to std::string it would produce the content as the equivalent std::sprintf . C++11 提供了std::to_string ,可用于将数字转换为std::string ,它会将内容生成为等效的std::sprintf Most likely this would be used in place of either std::ostringstream or snprintf .这很可能会用来代替std::ostringstreamsnprintf This is more of a convenience, there is probably not much of performance difference and we can see from the Fast integer to string conversion in C++ article there are probably much faster alternatives available if performance is the main concern:这更方便,可能没有太大的性能差异,我们可以从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. Using constexpr in place of template meta-programming 3. 使用 constexpr 代替模板元编程

If you are dealing with literals there may be cases where using constexpr functions over template meta-programming may produce code that is more clear and possibly compiles faster.如果您正在处理文字,则可能在模板元编程上使用 constexpr 函数可能会生成更清晰的代码并且可能编译得更快。 The article Want speed?文章想要速度? Use constexpr meta-programming! 使用 constexpr 元编程! provides an example of prime number determination using template meta-programming:提供使用模板元编程确定素数的示例:

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

and using constexpr functions:并使用 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);
}

The constexpr version is much shorter, easier to understand and apparently performs much better than the template meta-programming implementation. constexpr 版本比模板元编程实现更短、更容易理解并且显然执行得更好。

4. Using class member initialization to provide default values 4.使用类成员初始化提供默认值

As was recently covered in Has the new C++11 member initialization feature at declaration made initialization lists obsolete?正如最近在声明中新的 C++11 成员初始化特性是否使初始化列表过时? class member initialization can be used to provide default values and can simplify cases where a class has multiple constructors.类成员初始化可用于提供默认值,并可简化类具有多个构造函数的情况。

Bjarne Stroustrup provides a good example in the C++11 FAQ, he says: Bjarne Stroustrup在 C++11 FAQ 中提供了一个很好的例子,他说:

This saves a bit of typing, but the real benefits come in classes with multiple constructors.这节省了一些打字,但真正的好处来自具有多个构造函数的类。 Often, all constructors use a common initializer for a member:通常,所有的构造函数都为一个成员使用一个通用的初始化器:

and provides an example of members which have a common initializer:并提供了一个具有通用初始值设定项的成员示例:

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

and says:并说:

The fact that hash_algorithm and s each has a single default is lost in the mess of code and could easily become a problem during maintenance. hash_algorithm 和 s 每个都有一个默认值的事实在混乱的代码中丢失了,并且在维护期间很容易成为问题。 Instead, we can factor out the initialization of the data members:相反,我们可以分解数据成员的初始化:

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

Note, that in C++11 a class using in class member initializers is no longer an aggregate although this restriction is removed in C++14.请注意,在 C++11 中,在类成员初始化器中使用的类不再是聚合,尽管在 C++14 中删除了此限制。

5. Use fixed width integer types from cstdint instead of hand rolled typedefs 5. 使用来自 cstdint 的固定宽度整数类型,而不是手动滚动的 typedef

Since the C++11 standard uses C99 as a normative reference we get fixed width integer types , as well.由于 C++11 标准使用 C99 作为规范性参考,我们也得到了固定宽度整数类型 For example:例如:

int8_t
int16_t 
int32_t 
int64_t 
intptr_t

Although several of them an optional, for the exact width integer types the following from C99 section 7.18.1.1 applies:尽管其中几个是可选的,但对于精确宽度整数类型,C99 第7.18.1.1节中的以下内容适用:

These types are optional.这些类型是可选的。 However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two's complement representation, it shall define the corresponding typedef names.但是,如果实现提供了宽度为 8、16、32 或 64 位的整数类型,没有填充位,并且(对于有符号类型)具有二进制补码表示,则它应定义相应的 typedef 名称。

I would add delegating constructors and in-class member initializers to the list.我会将委托构造函数和类内成员初始化器添加到列表中。

Simplification By Using Delegating Constructors and In-Class Initialization通过使用委托构造函数和类内初始化进行简化

With C++03:使用 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_;
};

With C++11:使用 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_;
};

For-each syntax:对于每个语法:

std::vector<int> container;

for (auto const & i : container)
  std::cout << i << std::endl;
  1. Changing std::map to std::unordered_map and std::set to std::unordered_set where ever order of container's elements is irrelevant, enhances significantly the performance.std::map更改为std::unordered_map并将std::set更改为std::unordered_set ,其中容器元素的顺序无关紧要,可显着提高性能。
  2. Using std::map::at instead of using square bracket syntax insertion, when you want to avoid involuntary insertions.当您想避免非自愿插入时,使用std::map::at而不是使用方括号语法插入。
  3. Use alias templates when you want to typedef templates.当您想要typedef模板时使用别名模板。
  4. Use of initialization lists instead of for loops to initialize STL containers.使用初始化列表而不是 for 循环来初始化 STL 容器。
  5. Replace fixed size C arrays with std::array.用 std::array 替换固定大小的 C 数组。

Use the uniform initialization syntax for variable initialization使用统一初始化语法进行变量初始化

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

to avoid problems like c++'s most vexing parse (the rest of the reasons why the new way is superior are explained in the linked article by Herb Sutter)避免像c++ 最令人头疼的解析这样的问题(新方法优越的其余原因在 Herb Sutter 的链接文章中进行了解释)

This blog post proposes the Rule of Zero if all ownerships into a class follow the RAII principle, allowing to get rid of the Rule of Three/Four/Five in C++11.如果一个类的所有所有权都遵循 RAII 原则,则这篇博文提出了零规则,从而可以摆脱 C++11 中的三/四/五规则。

However, Scott Meyers shows here that not writing explicitly the destructor, copy/move constructors and assignment operators can induce subtle problems if you slightly change your code (say, for debugging).但是,Scott Meyers 在这里表明,如果您稍微更改代码(例如,为了调试),不显式编写析构函数、复制/移动构造函数和赋值运算符可能会导致微妙的问题。 He then recommends to explicitly declare default (C++11 feature) these functions:然后他建议显式声明默认(C++11 特性)这些函数:

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

Feature: std::move特征: std::move

"Express clear difference between the copying and moving the resources" “表达复制和移动资源之间的明显区别”

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.
  1. Prefer scoped enums to unscoped enums更喜欢有范围的枚举而不是无范围的枚举

    • In C++98 the enums, there is no scoped for enums like the following code snippet.在 C++98 的枚举中,没有像下面的代码片段那样的枚举范围。 The names of such enumerators belong to the scope containing enum, namely nothing else in that scope may have the same name.这些枚举器的名称属于包含枚举的范围,即该范围内的任何其他内容都不能具有相同的名称。

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

      However, in C++11, the scoped enums can fix this issue.但是,在 C++11 中, scoped enums可以解决此问题。 scoped enum are declared var enum class . 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
    • The enumerators of scope enums are more strongly typed. scope enums的枚举器类型更强。 But, the enumerators of unscoped enums implicitly convert to other types但是,无范围枚举的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 !! }

      However, scoped enums will be failed in this case.但是,在这种情况下, 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 !! }

      Fix it through static_cast通过static_cast修复它

      if (static_cast<double>(c) < 10.1) { auto vec = getVector(static_cast<std::size_t>(c)); }
    • unscoped enums may be forward-declared.无范围的unscoped enums可以前向声明。

       enum Color; // error!! enum class Color; // fine
    • Both scoped and unscoped enums support specification of the underlying type. scoped和非unscoped枚举都支持基础类型的规范。 The default underlying type for scoped enums is int . scoped enums的默认基础类型是int Unscoped enums have no default underlying type. Unscoped enums没有默认的基础类型。

  2. Using Concurrency API使用并发 API

    • Prefer task-based to thread-based更喜欢基于任务而不是基于线程

      If you want to run a function doAsyncWork asynchronously, you have two basic choices.如果你想异步运行一个函数doAsyncWork ,你有两个基本的选择。 One is thread-based一种是基于线程的

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

      The other is task-based .另一种是基于任务的

       auto fut = std::async(doAsyncWork);

      Obviously, we can get the return value of doAsyncWork through task-based more easily than thread-based .显然,我们可以通过基于任务的方式比基于线程的方式更容易地获取doAsyncWork的返回值。 With the task-based approach, it's easy, because the future returned from std::async offers the get function.使用task-based方法很容易,因为从std::async返回的 future 提供了 get 函数。 The get function is even more important if doAsyncWork emits an exception, because get provides access to that, too.如果doAsyncWork发出异常, get函数就更重要了,因为get也提供了对它的访问。

    • Thread-based calls for manual management of thread exhaustion, oversubscription, load balancing, and adaptation to new platforms. Thread-based调用,用于手动管理线程耗尽、超额订阅、负载平衡和适应新平台。 But Task-based via std::async with the default launch policy suffers from none of these drawbacks.但是使用默认启动策略Task-based std::async没有这些缺点。

    Here are several links:这里有几个链接:

    Concurrency In C++ C++ 中的并发

    C/C++ Programming Abstractions for Parallelism and Concurrency 并行和并发的 C/C++ 编程抽象

Optimize simple mathematical functions with constexpr, especially if they are called inside inner loops.使用 constexpr 优化简单的数学函数,尤其是在内部循环中调用它们时。 This would allow the compiler to calculate them at compilation saving you time这将允许编译器在编译时计算它们,从而节省您的时间

Example例子

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

Another example is to use std::enable_if to limit the allowed template parameters types in a particular template function/class.另一个例子是使用std::enable_if来限制特定模板函数/类中允许的模板参数类型。 This would make your code safer (in case you haven't use SFINAE to constrains the possible template arguments in your old code) when you implicit assume some property about the template types and it is just one extra line of code当您隐式假设有关模板类型的某些属性并且它只是一行额外的代码时,这将使您的代码更安全(如果您没有使用 SFINAE 来约束旧代码中可能的模板参数)

example:例子:

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: If you have a small array where the size is known at compile time and you you want to avoid the overhead of the heap allocation in std::vector (meaning: you want the array on the stack), you only choice in C++03 was to use c-style arrays.更新 1:如果您有一个在编译时已知大小的小数组,并且您希望避免 std::vector 中的堆分配开销(意思是:您希望堆栈上的数组),您只能选择C++03 是使用 c 风格的数组。 Change that to std::array .将其更改为std::array It is a simple change that provides you a lot of the functionally present in std::vector + stack allocation (much faster than heap allocation as I said before).这是一个简单的更改,为您提供了 std::vector + 堆栈分配中的许多功能(比我之前所说的堆分配快得多)。

Use smart pointers.使用智能指针。 Notice that there are still good reason to have naked pointers in some cases, the best way to check if a pointer should be smart is to look for the uses of delete on it.请注意,在某些情况下仍然有充分的理由使用裸指针,检查指针是否应该是智能的最好方法是寻找在其上使用delete的方法。

There should be no reason to use new either.也不应该有理由使用new Replace every new with make_shared or make_unique .make_sharedmake_unique替换每个new的。

Unfortunately make_unique didn't make it in the C++11 standard , the best solution IMO is to implement it yourself( see previous link ), and put some macros to check for the __cplusplus version ( make_unique is available in C++14).不幸的是make_unique没有在 C++11 标准中实现,最好的解决方案是 IMO 自己实现它(参见上一个链接),并放置一些宏来检查__cplusplus版本( make_unique在 C++14 中可用) .

Using make_unique and make_shared is really important in order to make your code exception safe.为了使您的代码异常安全,使用make_uniquemake_shared非常重要。

Use of override keyword使用覆盖关键字

Mark virtual functions in derived classes as override (if they indeed override of course).将派生类中的虚函数标记为覆盖(当然,如果它们确实覆盖)。 This can protect against introducing bugs in the future, eg by changing the signature of a virtual function in a base class and forgetting to change the signature in all derived classes accordingly.这可以防止将来引入错误,例如通过更改基类中的虚拟函数的签名并相应地忘记更改所有派生类中的签名。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM