名前付け:実装者名とユーザー名

7 月に名前の付け方 (の特定の部分) についてこのブログ投稿を書きたかったのですが、皮肉なことに、説明したい症状の名前がありませんでした。 CppCon で名前を挙げて、ようやく自分の考えを書き留める時間ができました.

そこで、ネーミングについて書きたいと思います。特に、名前が関数の機能を完全に説明している場合があるにもかかわらず、まったく役に立たないという現象についてです。

ケース スタディ 1:06

C++20 では、ヘッダー 13 にいくつかのビット操作関数が追加されています。 .そのうちの 1 つが 23 です。 .次のようになります:

int log2p1(int i)
{
    if (i == 0)
        return 0;
    else
        return 1 + int(std::log2(x)); 
}

これは基本的に 2 進対数に 1 を加えたものを返すため、31 という名前が付けられています。 .

これは便利そう…?

です。4853 を格納するために必要なビット数です。 .これは非常に便利な機能ですが、名前を見ただけではわかりません。

ケース スタディ 2:67

C++ オブジェクト モデルに関する簡単な復習:ポインターがある場合、そのポインターが配列の一部である場合にのみ、ポインター演算を行うことができます。隣接するオブジェクトがないためです。

int obj = 0;
int* ptr = &obj;

++ptr; // UB

しかし、これはたくさん 70 のこの潜在的な単純化された実装を検討してください。 :

void reserve(std::size_t n)
{
    // allocate new memory for our objects
    auto new_memory = (T*) ::operator new(n * sizeof(T));

    // move objects from old buffer to new buffer
    …

    // update buffer
    auto size = this->size();
    begin_ = new_memory;            // UB
    end_   = new_memory + size;     // UB
    end_capacity_ = new_memory + n; // UB
}

メモリを割り当て、オブジェクトを移動し、新しいメモリを指すようにポインタを更新しています。ただし、この関数のほとんどすべての行は未定義の動作です:配列ではないメモリでポインタ演算を実行しています!

ここでの問題は、明らかにプログラマーの問題ではありません。これは明らかに許可されるべきですが、C++ 標準自体の問題です。そのため、P0593 は、83 のような特定の関数を指定して標準を修正することを提案しています。 、 99 –必要に応じて、返されたメモリに配列を自動的に作成する機能。次に、配列へのポインターを取得します (例:102) オブジェクト)、ポインター演算を安全に実行できます。

ポインター演算を行う必要がある状況にある場合がありますが、暗黙的にオブジェクトを作成する特殊な関数のいずれかから取得されたものではないメモリがあります。たとえば、 110 を記述する場合 メモリ アロケータの機能 – デッド メモリが与えられ、その内部にオブジェクトは存在しませんが、ポインタ演算を実行する必要があります。そのために、P0593 は関数 120 を提案するために使用されました (および 135 とも呼ばれる別の関数 、しかしここではそれについては話していません。この関数を呼び出しても、物理コンピューターには実際の影響はありませんが、抽象マシンの目的でポインター演算を可能にするために必要なオブジェクトが作成されます。

そして 143 はプレースホルダー名でした。

そこでケルンで、LEWG はこの関数の新しい名前を見つける任務を負いました.2 つの候補は 153 でした. と 161 –なぜなら、まさにそれが関数の機能だからです。

私はそれらの名前が好きではありませんでした.

ケーススタディ 3:175

184 があります 範囲をその場でソートします:

std::vector<int> vec = {3, 1, 5, 4, 2};
std::sort(vec.begin(), vec.end());
// vec == {1, 2, 3, 4, 5}

199 もあります 範囲の一部をその場でソートします:

std::vector<int> vec = {3, 1, 5, 4, 2};
std::partial_sort(vec.begin(), vec.begin() + 3, vec.end());
// vec == {1, 2, 3, ?, ?} (don't know whether it is 4,5 or 5,4)

そして 203 があります 範囲の一部を並べ替えますが、インプレースではありません:

const std::vector<int> vec = {3, 1, 5, 4, 2};
std::vector<int> out;
out.resize(3);
std::partial_sort_copy(vec.begin(), vec.end(),
                       out.begin(), out.end());
// out == {1, 2, 3}

ケイトは 218 と主張します は理想的とは言えない名前ですが、同意します。

実装者名とユーザー名

上記の名前はどれも悪いものではありません :関数が何をするかについての完全に有効な説明です。 .229 238 を計算します 、 240 暗黙的にオブジェクトを作成し、251 部分的な並べ替えを行いますが、出力をコピーします。

しかし、私はそれらの名前のすべてが嫌いです。なぜですか?

役に立たないので、私はそれらの名前が嫌いです .はい、内容を教えてくれます 関数は実際に機能しますが、これは実際に必要な情報ではありません!

「この時点で、2 進対数に 1 を足した値を計算する必要がある」と考えているのではなく、「この値を格納するために必要なビット数を知る必要がある」と考えているのです。関数は 264 のようなものを呼び出しました 、278 ではありません .「binary logarithm plus 1」に接続するまでに、あなたはすでにそれを自分で書いています (そして、おそらく特別な大文字小文字のゼロを忘れているでしょう).そして、たとえ 288 、次にコードを見る人 (または将来のあなた) は、2 進対数とビット幅を関連付ける必要があります。290 のようなものです。 より自明な名前になります.

同様に、「暗黙的にオブジェクトを作成」したり、コピーで部分的な並べ替えを実行したりしたくない場合は、メモリを再利用したり、上位 N 個の値を並べ替えたりする必要があります。302 のようなものです。 、これは 311 の別の候補名でした 、または 327 より直感的な名前になります。

Kate は implementer name という用語を使用しました 330 の説明用 、しかし 343 にも適用されます と 357 実装を見ると、これらは完全に自然な名前です。 関数の。

ただし、ユーザー名ではありません :ユーザーがこの関数を説明するために使用する名前。ユーザーは、欲しいものを説明する関数名を探しています。 、あなたは方法を気にしません 関数が実装されています。あなたがしようとしていることを達成する方法で関数に名前を付けるでしょう - 361370 、または 388 を取得します .

関数の仕様を見て、それに基づいて名前を付けるだけで、実装者の視点とユーザーの視点の間に断絶が生じる可能性があります。関数がどのように使用されるかを常に心に留めておく必要があります。

明らかなガイドラインのように聞こえますが、394 を見るだけで 、それは明らかに完了していませんでした.そして悲しいことに、それは必ずしもそれほど単純ではありません.

ケース スタディ 4:405

これで 416 にたどり着きます つまり、425 と同じです。 、436 への C++20 の追加 .すべての命名規則によると、443 ひどい名前です.誰かがそれについてすでに知っていない限り、彼らは関数が何をするのか推測することができません.それは紛らわしい略語を使用するだけではありません 463 とは何の関係もありません )、フルネーム (人口数) もあまり役に立ちません。

しかし、それは関数の完全な説明です.What does 479 do?482まで下げる

492 は実装者名です。

それでも、ここでは実装者とユーザーの間の断絶はそれほど不快ではありません:505 設定されたビットの数をカウントする関数の受け入れられた名前。ビット操作を行っていて、ドメインについて知っている場合、これが到達する名前です。

ハッピーエンド?

P1956 (数日後に公開されます) は 515 の名前変更を提案しています 526 へ .C++20 に適用される予定です。

ケルンでは、LEWG はどちらの 534 も選択しませんでした 549 でもありません 552 の場合 、代わりに関数を完全に削除することにしました。バイト配列のplacement-newを呼び出すことで同じことが達成できるため、関数は必要ありません。呼び出しほど意図が明確にならないため、嫌いです561 (私のお気に入りでした).

そしてもちろん、578 名前を変更することはできません。1998 年から C++ の一部になっています。 修正されます。

名前を付けるときは、それがどのように使用されるか、ユーザーが何を達成したいかを念頭に置いてください。ケイトが言ったように:名前付けには共感が必要です .