多くの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が (リファクタリングの精神 :コードをより明確に、よりクリーンに、よりシンプルかつエレガントに。
委任コンストラクターとクラス内メンバー初期化子をリストに追加します。
委任コンストラクターとクラス内初期化を使用した簡略化
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. 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名を定義する必要があります。
For-each構文:
std::vector<int> container;
for (auto const & i : container)
std::cout << i << std::endl;
このブログ投稿は、クラスのすべての所有権が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;
widget w(x); // old
widget w{x}; // new
c ++の最も厄介な解析 などの問題を回避するには(新しい方法が優れているその他の理由については、Herb Sutterによるリンクされた記事で説明されています)
std::map
をstd::unordered_map
に、std::set
をstd::unordered_set
に変更すると、コンテナの要素の順序が関係ない場合に、パフォーマンスが大幅に向上します。std::map::at
を使用します。typedef
テンプレートが必要な場合は、エイリアステンプレートを使用します。機能: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.
スコープのない列挙型よりもスコープのある列挙型を優先する
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
には、デフォルトの基本タイプはありません。
同時実行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
には、これらの欠点はありません。
ここにいくつかのリンクがあります:
特に内部ループ内で呼び出される場合は特に、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 +スタック割り当てに機能的に存在する多くの機能を提供する単純な変更です(前述のヒープ割り当てよりもはるかに高速です)。
スマートポインターを使用します。場合によってはネイキッドポインターを使用する十分な理由があることに注意してください。ポインターがスマートであるかどうかを確認する最善の方法は、delete
の使用を探すことです。
new
を使用する理由もないはずです。すべてのnew
をmake_shared
またはmake_unique
に置き換えます。
残念ながらmake_unique
C++ 11標準では実現できませんでした 、IMOを自分で実装するのが最善の解決策です(前のリンクを参照)、そしていくつかのマクロを配置して__cplusplus
バージョンをチェックします(make_unique
はC++ 14で使用可能です)。
コードの例外を安全にするには、make_unique
とmake_shared
を使用することは本当に重要です。
overrideキーワードの使用
派生クラスの仮想関数をオーバーライドとしてマークします(もちろん、実際にオーバーライドされている場合)。これにより、将来バグが発生するのを防ぐことができます。基本クラスの仮想関数のシグネチャを変更し、それに応じてすべての派生クラスのシグネチャを変更するのを忘れます。