用C ++重构11

鉴于由c ++提供的大量程序员所提供的新工具集,针对代码简化,expression性,效率,浏览旧代码并进行调整(一些毫无意义,一些成功)来实现其目标。 尽pipe不要在这样的劳动中浪费太多时间,而只是做出非侵入性和自我包含的变化,那么最佳实践是什么?

让我把这个显而易见的事

  • 使用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); 
  • 使用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); 
  • 为了使一个类不可inheritance,只需声明它为“final”并删除实现了这种行为的代码。http://www.parashift.com/c++-faq/final-classes.html

  • 使用delete关键字可以显式隐藏构造函数/析构函数,而不是将其声明为私有的(例如创build基于堆的对象的代码,不可复制的对象等)

  • 为了简化单个STLalgorithm的执行,创build一些简单的函子(除了减less代码混乱之外,还可以保证内联调用)

  • 通过使用智能指针来简化对象的RAII包装

  • 摆脱bind1st,bind2nd,只使用绑定

  • 将types特征的手写代码(Is_ptr_but_dont_call_for_const_ptrs <>等:)replace为由<type_traits>提供的标准代码

  • 停止包含函数的boost头文件现在在STL中实现(BOOST_STATIC_ASSERT vs static_assert)

  • 提供移动语义到类(虽然这不会被认为是一个脏/快速/简单的变化)

  • 在可能的地方使用nullptr而不是NULLmacros,并删除用0填充的指针容器的代码转换为对象types

     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); 
  • 清除vector数据访问语法

     std::vector<int> vec; &vec[0]; // access data as a C-style array vec.data(); // new way of saying the above 
  • noexceptreplacethrow()(除了避免过时的exception说明,你会得到一些速度的好处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为容器中的临时文件,并希望优化器将文件拷贝掉,在可用的情况下使用“emplace”函数,以便将参数完美地转发并直接将对象构造到容器中所有。

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

UPDATE

Shafik Yaghmour的回答正确地被授予观众最大的认同。

R Sahu的回答是我所接受的,因为它所提出的特征组合体现了重构精神 :使代码更清晰,更简单,更优雅。

我会添加委托构造函数和类内成员初始化到列表中。

使用委托构造函数和类内初始化简化

用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()replace为随机头中可用的所有选项。 在很多情况下replacerand()应该是直接的。

Stephan T. Lavavej可能会把他的陈述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 ; } 

并使用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和 friends 。 这是最近在SO问题中被覆盖了为什么std :: shuffle方法在C ++ 14中被弃用? 。

请注意,这些分发不能保证在各个平台上保持一致 。

2.使用std :: to_string而不是std :: ostringstream或sprintf

C ++ 11提供了std :: to_string ,它可以用来将数字转换为std :: string ,它将产生相当于std :: sprintf的内容 。 这很可能会被用来代替std :: ostringstream或者snprintf 。 这更方便,可能没有太大的性能差异,我们可以从快速整数到C ++文章中的string转换中看到,如果性能是主要关心的话,可能有更快的select可用:

 #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版本比模板元编程实现要短得多,更容易理解,并且performance得更好。

4.使用类成员初始化来提供默认值

正如最近在新的C ++ 11成员初始化特性中声明的初始化列表已经被覆盖了吗? 类成员初始化可以用来提供默认值,并可以简化一个类有多个构造函数的情况。

Bjarne Stroustrup在C ++ 11 FAQ中提供了一个很好的例子,他说:

这节省了一些input,但真正的好处来自多个构造函数的类。 通常,所有构造函数都为一个成员使用一个通用的初始化方法:

并提供了一个具有共同初始值的成员的例子:

 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中,在类成员初始化器中使用的类不再是聚合,尽pipe在C ++ 14中删除了这个限制。

5.使用cstdint中的固定宽度整数types而不是手动滚动typedef

由于C ++ 11标准使用C99作为标准参考,所以我们也得到了固定宽度的整数types 。 例如:

 int8_t int16_t int32_t int64_t intptr_t 

虽然其中有几个是可选的,但是对于确切的宽度整数types,C99第7.18.1.1节中的以下7.18.1.1适用:

这些types是可选的。 但是,如果一个实现提供宽度为8,16,32或64位的整数types,没有填充位,并且(对于有符号types)有二进制补码表示,则它应该定义相应的typedef名称。

For-each语法:

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

使用统一的初始化语法进行variables初始化

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

为了避免像c ++最令人头疼的parsing (新的方式优越的原因的其他原因解释在Herb Sutter的链接文章)

这个博客文章提出了“零规则”,如果所有的所有者都遵循RAII原则,允许在C ++ 11中摆脱三/四/五规则。

然而,Scott Meyers 在这里表明,如果你稍微改变你的代码(比如debugging),不要显式地写析构函数,复制/移动构造函数和赋值操作符会引发细微的问题。 然后他build议显式声明这些函数的默认 (C ++ 11function):

 ~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. 
  1. std::map更改为std::unordered_map ,将std::set更改为std::unordered_set容器元素的顺序无关紧要,从而显着提高了性能。
  2. 当你想避免不自主的插入时,使用std::map::at而不是使用方括号语法插入。
  3. 当你想要typedef模板时使用别名模板。
  4. 使用初始化列表而不是for循环来初始化STL容器。
  5. 用std :: arrayreplace固定大小的C数组。

使用constexpr优化简单的math函数,尤其是在内部循环内部调用时。 这将允许编译器在编译时计算它们,节省您的时间

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

另一个例子是使用std::enable_if在特定的模板函数/类中限制允许的模板参数types。 这将使你的代码更安全(如果你没有使用SFINAE来限制旧代码中的可能的模板参数),当你隐式地假设一些关于模板types的属性,它只是一个额外的代码行

例:

 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(意味着:你想要堆栈中的数组)的开销,那么你只能selectC ++ 03是使用c风格的数组。 将其更改为std::array 。 这是一个简单的改变,它提供了很多std :: vector +堆栈分配function(比我之前说的快得多)。

使用智能指针。 请注意,在某些情况下,仍然有充分的理由使用裸指针,检查指针是否智能的最好方法是查找delete的用法。

应该没有理由使用new 。 用make_sharedmake_uniquereplace每个new

不幸的是, make_unique 并没有在C ++ 11标准中实现 ,最好的解决scheme是自己实现它( 见前面的链接 ),并放入一些macros来检查__cplusplus版本( make_unique在C ++ 14中可用) 。

使用make_uniquemake_shared非常重要的,以使您的代码exception安全。

  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器的types更强。 但是,非unscoped enums隐式转换为其他types

       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枚举都支持基础types的规范。 scoped enums的默认基础types是intUnscoped enums没有默认的基础types。

  2. 使用并发API

    • 首选基于线程的任务

      如果你想asynchronous运行一个函数doAsyncWork ,你有两个基本的select。 一个是基于线程的

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

      另一个是基于任务的

       auto fut = std::async(doAsyncWork); 

      显然,我们可以比基于线程的更容易地通过基于任务的获取doAsyncWork的返回值。 使用task-based方法很容易,因为从std::async返回的将来会提供get函数。 如果doAsyncWork发出exception,则get函数更为重要,因为get提供doAsyncWork的访问。

    • Thread-based调用可以手动pipe理线程耗尽,超额预订,负载平衡以及适应新平台。 但是Task-based std::async与默认的启动策略没有任何的缺点。

    这里有几个链接:

    并发在C ++中

    用于并行和并发的C / C ++编程抽象

使用覆盖关键字

将派生类中的虚拟函数标记为覆盖(如果它们确实被覆盖)。 这可以防止未来引入错误,例如,通过更改基类中的虚函数的签名,并忘记相应地更改所有派生类中的签名。