用C++ 11重构

Nik*_*iou 68 c++ refactoring c++11

鉴于c ++许多程序员提供的新工具集,旨在实现代码简化,表达性,效率,浏览旧代码并进行调整(有些无意义,有些成功)以实现他们的目标.尽管不要在这些工作上浪费太多时间,只是做出非侵入性和自包含的变化,但最佳做法是什么?

让我勾勒出明显的:

  • 使用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);
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用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);
    
    Run Code Online (Sandbox Code Playgroud)
  • 要使类不可继承,只需将其声明为"final"并删除实现此类行为的代码http://www.parashift.com/c++-faq/final-classes.html

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

  • 创建简单的仿函数只是为了将单个STL算法的执行变为lambda函数(除了减少代码混乱,你还能保证内联调用)

  • 仅使用智能指针简化对象的RAII包装

  • 摆脱bind1st,bind2nd只需使用bind

  • <type_traits>提供的标准代码替换类型特征的手写代码(Is_ptr_but_dont_call_for_const_ptrs <>等:))

  • 停止包含现在在STL中实现的函数的boost标头(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);
    
    Run Code Online (Sandbox Code Playgroud)
  • 清除矢量数据访问语法

    std::vector<int> vec;
    &vec[0];    // access data as a C-style array
    vec.data(); // new way of saying the above
    
    Run Code Online (Sandbox Code Playgroud)
  • 使用noexcept替换throw()(除了避免使用已弃用的异常指令,您可以获得一些速度优势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
    
    Run Code Online (Sandbox Code Playgroud)
  • 替换你在容器中推送一个临时代码的代码,并希望优化器能够在可用的情况下使用"emplace"函数来消除副本,以便完美地转发参数并直接将对象构造到容器中而无需临时所有.

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

UPDATE

Shafik Yaghmour的答案理所当然地获得了观众最大的认可.

R Sahu的答案是我接受的答案,因为它提出的功能组合体现了重构精神:使代码更清晰,更清晰,更简单和优雅.

Sha*_*our 29

1.取代兰特

C++ 11中的一大收获必须是替换随机头中rand()可用的所有选项的使用.在许多情况下更换应该是直截了当的.rand()

Stephan T. Lavavej可能认为这一点最强,他的演示兰德()被认为是有害的.这些示例显示了[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 ;
}
Run Code Online (Sandbox Code Playgroud)

并使用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 ;
}
Run Code Online (Sandbox Code Playgroud)

随之而来的应该是从std :: random_shuffle转移std :: shuffle,这是为了弃用rand和Friends.最近在SO问题中讨论了为什么在C++ 14中不推荐使用std :: shuffle方法?.

请注意,不保证分布在不同平台保持一致.

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

C++ 11提供了std :: to_string,它可用于将数字转换为std :: string,它将生成内容作为等效的std :: sprintf.很可能这将用于代替std :: ostringstreamsnprintf.这更方便,可能没有太大的性能差异,我们可以从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 ;
}
Run Code Online (Sandbox Code Playgroud)

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 };
};
Run Code Online (Sandbox Code Playgroud)

并使用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);
}
Run Code Online (Sandbox Code Playgroud)

constexpr版本更短,更容易理解,并且显然比模板元编程实现更好.

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

正如最近所述,在声明中新的C++ 11成员初始化功能使初始化列表过时了吗?类成员初始化可用于提供默认值,并可简化类具有多个构造函数的情况.

Bjarne Stroustrup在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
};
Run Code Online (Sandbox Code Playgroud)

并说:

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
};
Run Code Online (Sandbox Code Playgroud)

请注意,在C++ 11中,在类成员初始值设定项中使用的类不再是聚合,尽管在C++ 14中删除了此限制.

5.使用cstdint中的固定宽度整数类型而不是手动压缩的typedef

由于C++ 11标准使用C99作为规范参考,我们也得到固定宽度整数类型.例如:

int8_t
int16_t 
int32_t 
int64_t 
intptr_t
Run Code Online (Sandbox Code Playgroud)

虽然其中有几个是可选的,但对于精确的宽度整数类型,C99部分中的以下内容7.18.1.1适用:

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


R S*_*ahu 18

我会将委托构造函数和类内成员初始值设定项添加到列表中.

通过使用委托构造器和类内初始化来简化

使用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_;
};
Run Code Online (Sandbox Code Playgroud)

使用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_;
};
Run Code Online (Sandbox Code Playgroud)


voo*_*ack 12

For-each语法:

std::vector<int> container;

for (auto const & i : container)
  std::cout << i << std::endl;
Run Code Online (Sandbox Code Playgroud)

  • 我无法理解为什么人们仍然喜欢基于循环的迭代器,for_each with lambdas等.基于循环的范围导致更少的代码,并且它显然更容易阅读. (2认同)
  • 好吧,当您遍历它时必须修改容器时,不能使用基于范围的迭代,并且迭代器通常会提供更多控制.除此之外,我认为没有理由不使用它们. (2认同)

小智 9

使用统一初始化语法 进行变量初始化

widget w(x); // old
widget w{x}; // new
Run Code Online (Sandbox Code Playgroud)

避免像c ++最令人烦恼的解析之类的问题(Herb Sutter在链接文章中解释了新方法优越的其余原因)

  • 除非`widget`有一个带有`std :: initializer_list`的构造函数,否则这是个好主意. (6认同)

Flo*_*oux 9

如果所有的所有权都遵循RAII原则,那么这篇博客文章提出了零规则,允许摆脱C++ 11中的三/四/五规则.

但是,Scott Meyers 在这里表明,如果稍微更改代码(例如,用于调试),则不明确地编写析构函数,复制/移动构造函数和赋值运算符会引发细微问题.然后他建议显式声明这些函数的默认(C++ 11特性):

~MyClass()                           = default;
MyClass( const MyClass& )            = default;
MyClass( MyClass&& )                 = default;
MyClass& operator=( const MyClass& ) = default;
MyClass& operator=( MyClass&& )      = default;
Run Code Online (Sandbox Code Playgroud)


101*_*010 9

  1. 更改std::mapstd::unordered_mapstd::setstd::unordered_set哪里容器的元素永远顺序是无关紧要的,显著提高了性能.
  2. std::map::at当您想要避免非自愿插入时,使用而不是使用方括号语法插入.
  3. 如果要模板,请使用别名typedef模板.
  4. 使用初始化列表而不是for循环来初始化STL容器.
  5. 用std :: array替换固定大小的C数组.

  • `std :: uordered_set` - >这是一个错字.1.这种改变只能提高足够大的地图和集合的性能(否则只会降低程序的效率).在迭代这些容器的情况下,它也可能降低性能. (5认同)

Man*_*mar 7

功能: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.
Run Code Online (Sandbox Code Playgroud)

  • 移动后,无法保证`tmp`处于"空状态",只是它是一个"有效但未指定"的状态.(实际上,具有小对象优化的高质量字符串实现可能会使`tmp'保持不变.) (2认同)

Viv*_*nda 5

使用constexpr优化简单的数学函数,特别是如果它们在内部循环中调用.这将允许编译器在编译时计算它们,从而节省您的时间

constexpr int fibonacci(int i) {
    return i==0 ? 0 : (i==1 ? 1 : fibonacci(i-1) + fibonacci(i-2));
}
Run Code Online (Sandbox Code Playgroud)

另一个示例是用于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
}
Run Code Online (Sandbox Code Playgroud)

更新1:如果你有一个在编译时已知大小的小数组,你想要避免std :: vector中堆分配的开销(意思是:你想要堆栈上的数组),你只能选择C++ 03是使用c风格的数组.改为std::array.这是一个简单的更改,为std :: vector +堆栈分配提供了很多功能(比我之前说的堆分配快得多).

  • 切勿以这种方式评估斐波那契数。 (2认同)

zan*_*ngw 5

  1. 优先于作用域枚举而不是无作用域枚举

    • 在C ++ 98枚举中,没有像以下代码段这样的枚举范围。此类枚举器的名称属于包含枚举的范围,即该范围内的其他名称均不得相同。

      enum Color{ blue, green, yellow };
      bool blue = false;    // error: 'blue' redefinition
      
      Run Code Online (Sandbox Code Playgroud)

      但是,在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
      
      Run Code Online (Sandbox Code Playgroud)
    • 的枚举数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 !!
      }
      
      Run Code Online (Sandbox Code Playgroud)

      但是,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 !!
      }
      
      Run Code Online (Sandbox Code Playgroud)

      通过修复 static_cast

      if (static_cast<double>(c) < 10.1) {
         auto vec = getVector(static_cast<std::size_t>(c));
      } 
      
      Run Code Online (Sandbox Code Playgroud)
    • unscoped enums 可能会被预先声明。

      enum Color;          // error!!
      enum class Color;    // fine
      
      Run Code Online (Sandbox Code Playgroud)
    • scopedunscoped枚举支持基础类型的规范。为默认的基本类型scoped enumsintUnscoped enums没有默认的基础类型。

  2. 使用并发API

    • 首选基于任务而不是基于线程

      如果要doAsyncWork异步运行函数,则有两个基本选择。一种是基于线程的

      int doAsyncWork();
      std::thread t(doAsyncWork);
      
      Run Code Online (Sandbox Code Playgroud)

      另一个是基于任务的

      auto fut = std::async(doAsyncWork);
      
      Run Code Online (Sandbox Code Playgroud)

      显然,与基于线程相比,doAsyncWork通过基于任务的返回值更容易。使用这种方法很容易,因为从中返回的未来提供了get函数。如果发出异常,则该功能甚至更为重要,因为它也提供对该异常的访问。task-basedstd::asyncgetdoAsyncWorkget

    • Thread-based要求手动管理线程耗尽,超额预订,负载平衡以及对新平台的适应。但是Task-based通过std::async默认启动策略,这些缺点都没有。

    这里有几个链接:

    C ++中的并发

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


归档时间:

查看次数:

5120 次

最近记录:

8 年,11 月 前