web-dev-qa-db-ja.com

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);
    
  • Cスタイルのコード行を生成するだけの複数の割り当てには、tieを使用します( 複数の値を構造体に一度に割り当てる方法は? =)

    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とstatic_assert)

  • クラスに移動セマンティクスを提供します(ただし、これはダーティ/クイック/イージーチェンジとは見なされません)

  • 可能な場合は、NULLマクロの代わりにnullptrを使用して、ポインターのコンテナーを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
    
  • コンテナーに一時をプッシュし、オプティマイザーがコピーを省略できるようにしたコードを、 "emplace"関数で置き換えます。引数を完全に転送し、一時的ではなくコンテナに直接オブジェクトを構築するため。

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

更新

Shafik Yaghmourの回答 は、聴衆に最も受け入れられた賞金を正しく授与されました。

R Sahuによる回答 は私の提案したものでした。提案されている機能のcombinationリファクタリングの精神 :コードをより明確に、よりクリーンに、よりシンプルかつエレガントに。

68

委任コンストラクターとクラス内メンバー初期化子をリストに追加します。

委任コンストラクターとクラス内初期化を使用した簡略化

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_;
};
18
R Sahu

1. Randの交換

C++ 11の大きな利点の1つは、Rand()の使用を ランダムヘッダー で使用可能なすべてのオプションに置き換えることです。多くの場合、Rand()の置き換えは簡単です。

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 に移動する必要があります Deprecate Rand and Friends への取り組みから生まれます。これは最近、SO質問 C++ 14でstd :: shuffleメソッドが非推奨になっている理由) で取り上げられました。

ディストリビューションが プラットフォーム間で一貫している であることが保証されていないことに注意してください。

2. std :: ostringstreamまたはsprintfの代わりにstd :: to_stringを使用する

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関数を使用すると、より明確なコードが生成され、コンパイルが高速になる可能性があります。記事 スピードが必要ですか?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メンバー初期化機能により、初期化リストは廃止されましたか? クラスメンバーの初期化を使用してデフォルト値を提供でき、クラスに複数のコンストラクターがある場合を簡略化できます。

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

そして言う:

Hash_algorithmとsのそれぞれにデフォルトが1つしかないという事実は、コードの混乱によって失われ、メンテナンス中に簡単に問題になる可能性があります。代わりに、データメンバーの初期化を除外できます。

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.手で転がされたtypedefの代わりに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名を定義する必要があります。

29
Shafik Yaghmour

For-each構文:

std::vector<int> container;

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

このブログ投稿は、クラスのすべての所有権がRAIIの原則に従っている場合、 Rule of Zero を提案し、C++ 11で3/4/4のルールを取り除くことができるようにします。

ただし、Scott Meyersは here を示しています。デストラクタ、コピー/移動コンストラクタ、および代入演算子を明示的に記述しないと、コードを少し変更すると(たとえば、デバッグのため)微妙な問題が発生する可能性があります。次に、これらの関数を明示的に宣言するdefault(C++ 11機能)ことをお勧めします。

~MyClass()                           = default;
MyClass( const MyClass& )            = default;
MyClass( MyClass&& )                 = default;
MyClass& operator=( const MyClass& ) = default;
MyClass& operator=( MyClass&& )      = default;
9
Florian Richoux

変数の初期化 には 均一な初期化構文 を使用します

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

c ++の最も厄介な解析 などの問題を回避するには(新しい方法が優れているその他の理由については、Herb Sutterによるリンクされた記事で説明されています)

9
user3316811
  1. std::mapstd::unordered_mapに、std::setstd::unordered_setに変更すると、コンテナの要素の順序が関係ない場合に、パフォーマンスが大幅に向上します。
  2. 不本意な挿入を避けたい場合は、角括弧構文挿入を使用する代わりにstd::map::atを使用します。
  3. typedefテンプレートが必要な場合は、エイリアステンプレートを使用します。
  4. Forループの代わりに初期化リストを使用して、STLコンテナーを初期化します。
  5. 固定サイズのC配列をstd :: arrayに置き換えます。
9
101010

機能: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
Mantosh Kumar
  1. スコープのない列挙型よりもスコープのある列挙型を優先する

    • C++ 98列挙型では、次のコードスニペットのような列挙型のスコープはありません。そのような列挙子の名前はenumを含むスコープに属します。つまり、そのスコープ内の他の何も同じ名前を持つことはできません。

      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を非同期で実行する場合、2つの基本的な選択肢があります。 1つはthread-basedです

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

      もう1つはtask-basedです。

      auto fut = std::async(doAsyncWork);
      

      明らかに、task-basedを通じてthread-basedより簡単にdoAsyncWorkの戻り値を取得できます。 task-basedアプローチを使用すると、std::asyncから返されるフューチャーがget関数を提供するため、簡単です。 getも例外へのアクセスを提供するため、doAsyncWorkが例外を発行する場合、get関数はさらに重要です。

    • Thread-basedは、スレッドの枯渇、オーバーサブスクリプション、ロードバランシング、および新しいプラットフォームへの適応の手動管理を要求します。ただし、デフォルトの起動ポリシーを使用したTask-based経由のstd::asyncには、これらの欠点はありません。

    ここにいくつかのリンクがあります:

    C++での同時実行性

    並列処理と並行処理のためのC/C++プログラミングの抽象化

5
zangw

特に内部ループ内で呼び出される場合は特に、constexprを使用して単純な数学関数を最適化します。これにより、コンパイラはコンパイル時にそれらを計算して時間を節約できます

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

別の例は、std::enable_ifを使用して、特定のテンプレート関数/クラスで許可されるテンプレートパラメータタイプを制限することです。これは、テンプレートタイプに関するいくつかのプロパティを暗黙的に想定し、コードの追加行が1つだけの場合に、コードをより安全にします(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のヒープ割り当てのオーバーヘッドを回避したい場合(つまり、スタックに配列が必要な場合)、 C++ 03はcスタイルの配列を使用することでした。これをstd::arrayに変更します。これは、std :: vector +スタック割り当てに機能的に存在する多くの機能を提供する単純な変更です(前述のヒープ割り当てよりもはるかに高速です)。

5
Vivian Miranda

スマートポインターを使用します。場合によってはネイキッドポインターを使用する十分な理由があることに注意してください。ポインターがスマートであるかどうかを確認する最善の方法は、deleteの使用を探すことです。

newを使用する理由もないはずです。すべてのnewmake_sharedまたはmake_uniqueに置き換えます。

残念ながらmake_uniqueC++ 11標準では実現できませんでした 、IMOを自分で実装するのが最善の解決策です(前のリンクを参照)、そしていくつかのマクロを配置して__cplusplusバージョンをチェックします(make_uniqueはC++ 14で使用可能です)。

コードの例外を安全にするには、make_uniquemake_sharedを使用することは本当に重要です。

4
sbabbi

overrideキーワードの使用

派生クラスの仮想関数をオーバーライドとしてマークします(もちろん、実際にオーバーライドされている場合)。これにより、将来バグが発生するのを防ぐことができます。基本クラスの仮想関数のシグネチャを変更し、それに応じてすべての派生クラスのシグネチャを変更するのを忘れます。

2
opetroch