アレイサイズ定数アンチパターン

記事の翻訳は、コース「C ++ Developer」の開始を見越して作成されましたプロフェッショナル」








Code Review StackExchangeの学生のコードや、他の人のかなりの数の教材(!)でさえよく見かける、アンチパターンに注意を向けたいと思います。たとえば、5つの要素の配列があります。そして、魔法の数が悪いので、「5」のカーディナリティを表す名前付き定数を導入します。



void example()
{
    constexpr int myArraySize = 5;
    int myArray[myArraySize] = {2, 7, 1, 8, 2};
    ...




しかし、解決策はまあまあです!上記のコードでは、5番目の数字が繰り返されています。最初は値myArraySize = 5で、次に実際に要素を割り当てるときにもう一度繰り返さますmyArray上記のコードは、メンテナンスの観点からは次のようにひどいものです。



constexpr int messageLength = 45;
const char message[messageLength] =
    "Invalid input. Please enter a valid number.\n";




-もちろん、私たちの誰もこれを書くことはありません。



繰り返されるコードは良くありません



上記の両方のコードスニペットで、配列の内容またはメッセージの文言を変更するたびに、1行ではなく2行のコードを更新する必要があることに注意してくださいメンテナがこのコードを誤って更新する方法の例を次に示します



   constexpr int myArraySize = 5;
-   int myArray[myArraySize] = {2, 7, 1, 8, 2};
+   int myArray[myArraySize] = {3, 1, 4};




上記のパッチは、配列の内容2,7,1,8,2から3,1,4変更しているように見えますが、そうではありません。実際に、彼はそれを変更3,1,4,0​​,0 -ゼロパディングと-メンテナを調整するために忘れてしまったので、によるとmyArraySizemyArray



信頼できるアプローチ



カウントに関しては、コンピューターはかなり上手です。だから、コンピュータを数えましょう!



int myArray[] = {2, 7, 1, 8, 2};
constexpr int myArraySize = std::size(myArray);




これで、1行のコードを変更するだけで、配列の内容をたとえば2,7,1,8,2から3,1,4に変更できます。変更をどこにでも複製する必要はありません。



さらに、myArray実際のコードは通常for、反復子の範囲基づくループやアルゴリズムを使用して操作するため、配列のサイズを格納するために名前付き変数はまったく必要ありません。



for (int elt : myArray) {
    use(elt);
}
std::sort(myArray.begin(), myArray.end());
std::ranges::sort(myArray);

// Warning: Unused variable 'myArraySize'




このコードの「悪い」バージョンはmyArraySize常に(宣言でmyArray使用されるため、プログラマーはそれが除外できることに気付かない可能性があります。「良い」バージョンでは、コンパイラはmyArraySize使用されていないものを簡単に検出できます。



これをどのように行うのstd::arrayですか?



プログラマーがダークサイドに向けて別の一歩を踏み出し、次のように書くことがあります。



constexpr int myArraySize = 5;
std::array<int, myArraySize> myArray = {2, 7, 1, 8, 2};




これは、少なくとも次のように書き直す必要があります。



std::array<int, 5> myArray = {2, 7, 1, 8, 2};
constexpr int myArraySize = myArray.size();  //  std::size(myArray)




ただし、最初の行の手動カウントを取り除く簡単な方法はありません。CTAD C ++ 17では次のように書くことができます



std::array myArray = {2, 7, 1, 8, 2};




ただし、これは配列が必要な場合にのみ機能しますたとえば、int配列short配列が必要な場合は機能しませんuint32_t



C ++ 20はstd :: to_arrayを提供します。これにより、次のように記述できます。



auto myArray = std::to_array<int>({2, 7, 1, 8, 2});
constexpr int myArraySize = myArray.size();




これによりC配列が作成され、その要素がに移動(move-construct)されることに注意してくださいstd::array。これまでのすべての例はmyArray、集約の初期化をトリガーし、アレイ要素を所定の位置にインスタンス化する中括弧付きの初期化子リストで初期化されました。



いずれにせよ、これらのオプションはすべて、古き良きCアレイ(テンプレートのインスタンス化を必要としない)と比較して、多数の追加のテンプレートインスタンスをもたらします。したがって、私T[]は新しいものを強く好みstd::array<T, N>ます。



C ++ 11とC ++ 14では、std::array言うことができるという人間工学的な利点がありましたarr.size()。しかし、C ++ 17が私たちに提供したとき、その利点は蒸発しましたstd::size(arr)インラインアレイの場合。std::array人間工学的な利点はもうありません。オブジェクト全体の変数セマンティクスが必要な場合に使用します(配列全体を関数に渡します!関数から配列を返します!=で配列を割り当てます!==!で配列を比較します)が、それ以外の場合は、の使用を避けることをお勧めしstd::arrayます。



同様に、std::listイテレーターの安定性、高速接着、要素を置き換えずに並べ替えるなどが必要な場合を除いて、を避けることをお勧めします。C++にこれらのタイプの場所がないと言っているわけではありません。私は彼らが「非常に特定のスキルのセット」を持っていると言っているだけです、そしてあなたがそれらのスキルを使わなければ、あなたはおそらく過払いです。




結論:馬の前でカートを囲わないでください。実際、カートは必要ないかもしれません。また、ゼブラを使用して馬の仕事をする必要がある場合は、ゼブラの前でカートをフェンスで囲うべきではありません。





続きを読む:






All Articles