C++ コア ガイドライン:式のその他のルール

この投稿の見出しが少し退屈であることは承知しています。正直なところ、この投稿はコードの衛生状態に関するものです。なぜなら、私は主にポインターについて書くからです。

今日の私の計画を見てみましょう。

  • ES.42:ポインタの使用を単純明快に保つ
  • ES.45:「魔法の定数」を避ける。記号定数を使用する
  • ES.47:06 を使用 18 ではなく または 21

非常に重要なルールから始めましょう。

ES.42:ポインタの使用をシンプルかつ直接的に保つ

ガイドラインの言葉を引用させてください:「複雑なポインター操作はエラーの主な原因です .". なぜ気にする必要があるのでしょうか? もちろん、私たちのレガシー コードには、次の例のような機能がたくさんあります:

void f(int* p, int count)
{
 if (count < 2) return;

 int* q = p + 1; // BAD

 int n = *p++; // BAD

 if (count < 6) return;

 p[4] = 1; // BAD

 p[count - 1] = 2; // BAD

 use(&p[0], 3); // BAD
}

int myArray[100]; // (1)

f(myArray, 100), // (2)

このコードの主な問題は、呼び出し元が C 配列の正しい長さを提供する必要があることです。そうでない場合、未定義の動作になります。

最後の行 (1) と (2) について数秒間考えてみてください。配列から始めて、関数 f に渡すことでその型情報を削除します。このプロセスは配列からポインターへの減衰と呼ばれ、多くのエラーの原因となっています。たぶん、私たちは悪い日を過ごし、要素の数を間違って数えたり、C 配列のサイズを変更したりしました。とにかく、結果は常に同じです:未定義の動作です。同じ議論が C 文字列にも当てはまります。

私たちは何をすべきか?適切なデータ型を使用する必要があります。ガイドラインでは、ガイドライン サポート ライブラリ (GSL) の gsl::spantype を使用することを提案しています。こちらをご覧ください:

void f(span<int> a) // BETTER: use span in the function declaration
{
 if (a.length() < 2) return;

 int n = a[0]; // OK

 span<int> q = a.subspan(1); // OK

 if (a.length() < 6) return;

 a[4] = 1; // OK

 a[count - 1] = 2; // OK

 use(a.data(), 3); // OK
}

罰金! gsl::span は実行時にその境界をチェックします。さらに、ガイドライン サポート ライブラリには、gsl::span の要素にアクセスするための無料の関数 at があります。

void f3(array<int, 10> a, int pos) 
{
 at(a, pos / 2) = 1; // OK
 at(a, pos - 1) = 2; // OK
}

私はあなたの問題を知っています。ほとんどの人は、ガイドライン サポート ライブラリを使用していません。問題ない。コンテナー std::array とメソッド std::array::at を使用して関数 f と f3 を書き直すのは非常に簡単です。

// spanVersusArray.cpp

#include <algorithm>
#include <array>

void use(int*, int){}

void f(std::array<int, 100>& a){

 if (a.size() < 2) return;

 int n = a.at(0); 

 std::array<int, 99> q;
 std::copy(a.begin() + 1, a.end(), q.begin()); // (1)

 if (a.size() < 6) return;

 a.at(4) = 1; 

 a.at(a.size() - 1) = 2;

 use(a.data(), 3); 
}

void f3(std::array<int, 10> a, int pos){
 a.at(pos / 2) = 1; 
 a.at(pos - 1) = 2; 
}

int main(){

 std::array<int, 100> arr{};

 f(arr);
 
 std::array<int, 10> arr2{};
 
 f3(arr2, 6);

}

std::array::at オペレーターは、実行時にその境界をチェックします。 pos>=size() の場合、std::out_of_range 例外が発生します。 spanVersusArray.cpp プログラムを注意深く見ると、2 つの問題に気付くでしょう。第一に、式 (1) は gsl::span バージョンよりも冗長であり、第二に、std::array のサイズは関数 f のシグネチャの一部です。これは本当に悪いです。 f は std::array 型でのみ使用できます。この場合、関数内の配列サイズのチェックは不要です。

あなたの救助のために、C++ にはテンプレートがあります。したがって、タイプの制限を簡単に克服できますが、タイプ セーフを維持できます。

// at.cpp

#include <algorithm>
#include <array>
#include <deque>
#include <string>
#include <vector>

template <typename T>
void use(T*, int){}

template <typename T>
void f(T& a){

 if (a.size() < 2) return;

 int n = a.at(0); 

 std::array<typename T::value_type , 99> q; // (4)
 std::copy(a.begin() + 1, a.end(), q.begin()); 

 if (a.size() < 6) return;

 a.at(4) = 1; 

 a.at(a.size() - 1) = 2;

 use(a.data(), 3); // (5)
}

int main(){

 std::array<int, 100> arr{}; 
 f(arr); // (1)
 
 std::array<double, 20> arr2{};
 f(arr2); // (2)
 
 std::vector<double> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
 f(vec); // (3)
 
 std::string myString= "123456789";
 f(myString); // (4)
 
 // std::deque<int> deq{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 // f(deq); // (5)
 
}

現在、関数 f は、さまざまなサイズとタイプの std::array (行 (1) と (2)) に対して機能しますが、std::vector(3) または std::string (4) に対しても機能します。このコンテナーには、データが連続したメモリ ブロックに格納されているという共通点があります。これは std::deque; を保持しません。したがって、式 (5) の a.data() の呼び出しは失敗します。 std::deque は、小さなメモリ ブロックの一種の二重リンク リストです。

式 T::value_type (5) は、各コンテナーの基になる値の型を取得するのに役立ちます。 T は関数テンプレート f の型パラメーターであるため、T はいわゆる従属型です。これが理由です。T::value_type が実際には型であるというヒントをコンパイラに与える必要があります:typename T::value_type.

ES.45:「魔法の定数」を避ける。記号定数を使用

これは明白です:シンボリック定数は魔法の定数以上のことを言います.

ガイドラインは、マジック定数で始まり、シンボリック定数で続き、範囲ベースの for ループで終わります。

for (int m = 1; m <= 12; ++m) // don't: magic constant 12
 cout << month[m] << '\n';



 // months are indexed 1..12 (symbolic constant)
constexpr int first_month = 1;
constexpr int last_month = 12;

for (int m = first_month; m <= last_month; ++m) // better
 cout << month[m] << '\n';



for (auto m : month) // the best (ranged-based for loop)
 cout << m << '\n';

範囲ベースの for ループの場合、off-by-one エラーを起こすことはできません。

ルール ES.47 に直接ジャンプさせてください。 ES.46を含めた変換ルールは別記事にまとめたいと思います。

ES.47:38 41 ではなく または 58

数値 0 またはマクロ NULL の代わりに nullptr を使用する理由はたくさんあります。特に、0 または NULL はジェネリックでは機能しません。これら 3 種類の null ポインターについては、既に記事を書いています。詳細は次のとおりです:Null ポインター定数 nullptr.

次は?

最新の C++ にはいくつの明示的なキャストがありますか?あなたの番号は 4 かもしれませんが、これは間違った番号です。 C++11 には、6 つの明示的なキャストがあります。 GSL を含めると、8 つの明示的なキャストがあります。次の投稿で8人のキャストについて書きます。