[海外動画紹介] あなたが止めるべき31の初心者っぽいC++の習慣

[31 nooby C++ habits you need to ditch - YouTube](https://www.youtube.com/embed/i_wDa2AS_8w)

この動画の解説が非常に参考になったので、勉強がてら紹介記事を書くことにしました。

完全翻訳ではなく、大雑把に要点のみ解説しています。気になる方は元動画をご参照ください。
登場するソースコードは下記のURLで入手可能です（解説コメント付き）。

https://github.com/mCodingLLC/VideosSampleCode/blob/master/videos/086_nooby_cpp_habits/main.cpp

翻訳内容に若干自信がない部分があるので、おかしな点があればご指摘いただけると助かります。

# 1. using namespace std; [^1]

```cpp
#include ...

using namespace std;

void using_namespace_std() {
    string s{"hello, world!"};
    cout << s << endl;
}
```

関数などのスコープ内に収まっている場合は問題ないが、グローバル空間やヘッダーに`using`宣言が定義されている場合、名前空間の衝突の原因になるので、このような使い方は避けた方が良い。[^2]
また、できればオブジェクト単位で個別に指定することを検討した方が良い。

[^1]: 訳注：元動画では構文としての`using namespace std`とその使用(using)が掛けられています。

[^2]: プレゼンテーション用の簡易コードなどでは使われる場合があります。

```cpp:修正例
#include ...

using std::string, std::cout, std::endl;

void using_namespace_std() {
    string s{"hello, world!"};
    cout << s << endl;
}
```

# 2. （ループの中などでの）`std::endl`の複数回使用

```cpp
void print_range(int start, int end) {
    for (auto i = start; i != end; ++i)
        std::cout << i << std::endl;
}
```

`std::endl`は実行毎に出力バッファの内容を逐一flushするので、実行時オーバーヘッドを減らすため、ループの途中などflushの必要がない箇所では改行文字を使用した方が良い。

```cpp:修正例
void print_range(int start, int end) {
    for (auto i = start; i != end; ++i)
        std::cout << i << '\n';
}
```

# 3. range-based forを使用すべき箇所でのindexループの使用

```cpp
void train_model(const std::vector<int> &data, auto &model) {
    for (std::size_t i = 0; i < data.size(); ++i) {
        model.update(data[i]);
    }
}
```

typoやバグを避けるため、ループカウンタを参照する必要がないのであればrange-based forを使用した方が良い。

```cpp:修正例
void train_model(const std::vector<int> &data, auto &model) {
    for (const auto &x: data)
        model.update(x);
}
```

index変数を使う必要がなくなり、typoやバグを仕込む可能性が少なくなる。

# 4. STLアルゴリズムの再発明

```cpp
void know_your_algorithms() {
    const std::vector<int> data = {-1, -3, -5, 8, 15, -1};

    // find the index of the first positive element
    std::size_t first_pos_idx;
    for (std::size_t i = 0; i < data.size(); ++i) {
        if (data[i] > 0) {
            first_pos_idx = i;
            break;
        }
    }
    // use first_pos_idx
}
```

実行しようとしている処理が単純な場合、すでに用意されているSTLアルゴリズムで代用できないか確かめた方が良い。
上記の例（先頭に最も近い正の要素のindexを取得）の場合、`std::find_if`関数が使用可能。

```cpp:修正例
void know_your_algorithms() {
    const auto is_positive = [](const auto &x) {
        return x > 0;
    };
    auto first_pos_it = std::find_if(
            data.cbegin(),
            data.cend(),
            is_positive);
    // or ranges even better
    // use first_pos_it
}
```

# 5. Cスタイルの配列の使用

```cpp
void f(int *arr, int n) {
    // ...
}
```

Cスタイルの配列を引数として渡した場合、先頭要素を示すポインタ型にキャストされるため、要素数を別途指定する必要がある。
代わりに`std::array`を使用した方が良い。

```cpp:修正例
template<std::size_t size>
void better_f(std::array<int, size> &arr) {
    // ...
}
```

イテレータや、C++20であれば`std::span`を使えば範囲を自由に指定できるようになる。

# 6. `reinterpret_cast`の使用

```cpp
template<typename T>
void print_bytes(const T &input, std::ostream &os = std::cout) {
    auto *bytes = reinterpret_cast<const std::byte *>(&input);
    os << "[";
    os << std::hex << std::noshowbase;
    for (std::size_t i = 0; i < sizeof(T); ++i) {
        if (i != 0)
            os << " ";
        os << std::setfill('0') << std::setw(2);
        os << static_cast<int>(bytes[i]);
    }
    os << "]\n";
}
```

C++において、`reinterpret_cast`した変数を安全に参照できるのは元の型に再変換した場合のみであり、それ以外のほとんどのケースでの参照結果は未保証である。[^3]
C++20でビット列を維持したまま型変換する`std::bit_cast`が実装されたので、変数のビット表現を得たい場合はそちらを使用した方が良い。

[^3]: 元動画では未定義動作となっていますが、C++20規格書では結果が未保証であるとされています。

```cpp:修正例
template<typename T>
void print_bytes_cpp20(const T &input, std::ostream &os = std::cout) {
    using bytearray = std::array<std::byte, sizeof(T)>;
    const auto &bytes = std::bit_cast<bytearray, T>(input);
    os << "[";
    os << std::hex << std::noshowbase;
    for (std::size_t i = 0; i < sizeof(T); ++i) {
        if (i != 0)
            os << " ";
        os << std::setfill('0') << std::setw(2);
        os << static_cast<int>(bytes[i]);
    }
    os << "]\n";
}

```

下記の様なケースにおいて、Cスタイルのキャストは`reinterpret_cast`として再解釈される可能性があるため、非推奨。

```cpp
void any_use_of_reinterpret_cast() {
    float y = .123f;
    long i = *(long *) &y;
    // ...
    y = *(float *) &i;
}
```


# 7. `const_cast`によるconst属性の除去

```cpp
const std::string &
more_frequent(const std::unordered_map<std::string, int> &word_counts,
              const std::string &word1,
              const std::string &word2) {
    auto &counts = const_cast<std::unordered_map<std::string, int> &>(word_counts);
    return counts[word1] > counts[word2] ? word1 : word2;
}
```

`const std::map`系のコンテナで`[]`演算子が使えない場合、const属性をキャストで除去するのではなく`at`メソッドを使用した方が良い。
`[]`演算子がconst参照でない理由については次項を参照。

# 8. `std::unordered_map`の`[]`演算子の挙動に対する誤解

```cpp
std::unordered_map<std::string, int>
count_words(const std::vector<std::string> words) {
    std::unordered_map<std::string, int> counts;
    for (const auto &word: words)
        counts[word]++;
    return counts;
}
```

`std::map`系のコンテナに対して`[]`演算子を用いて存在しないキーを参照した場合、valueをデフォルト値で初期化した上で要素を新規作成するため、`[]`演算子はconst属性ではない。

# 9. `const`属性の未使用

```cpp
void print_vec_one_per_line(std::vector<int> &arr) {
    for (const auto &x: arr) {
        std::cout << x << '\n';
    }
}
```

関数が参照先を変更しないことを明示するため、変更しないことが保証されている変数や引数には`const`属性を追加した方が良い。

# 10. 文字列リテラルのライフタイムに対する誤解

```cpp
const char *string_literal_lifetimes() {
    return "string literals";
}
```

文字列リテラルの値はプロセス終了時まで保持されるため、上記の関数は問題なく動作する。

# 11. 構造化束縛の未使用

```cpp
void loop_map_items() {
    std::unordered_map<std::string, std::string> colors = {
            {"RED",   "#FF0000"},
            {"GREEN", "#00FF00"},
            {"BLUE",  "#0000FF"}
    };

    for (const auto &pair: colors) {
        std::cout << "name: " << pair.first << ", hex: " << pair.second << '\n';
    }
```

C++17以降、構造化束縛を使用することにより、可読性を維持しつつメンバ変数に対する参照を複数まとめて初期化することができる。

```cpp:修正例
    for (const auto&[name, hex]: colors) {
        std::cout << "name: " << name << ", hex: " << hex << '\n';
    }
```

# 12. 出力用の参照渡しの使用

```cpp
void get_values_out_params(const int n, int &out1, int &out2) {
    // do stuff
    out1 = n;
    out2 = n + 1;
}
```

関数から複数の戻り値を返したい場合、参照渡しではなく専用の構造体を定義して戻り値として返した方が良い。

```cpp:修正例
struct Values {
    int x, y;
};

Values get_values_return_struct(const int n) {
    return {n, n + 1};
}
```


戻り値の構造体は自動的にmoveされるので、実行時オーバーヘッドは発生しない。

# 13. コンパイル時処理できる演算に対する実行時処理[^4]

```cpp
constexpr int sum_of_1_to_n(const int n) {
    return n * (n + 1) / 2;
}

void uses_sum() {
    const int limit = 1000;
    auto triangle_n = sum_of_1_to_n(limit);
    // use triangle_n...
}
```

`constexpr`属性を指定することで、関数（または変数）を定数式の内部で使用できるようになる。
実行時間短縮の他、テンプレート引数などの定数式が必要な箇所で関数を使用できるようになる。

[^4]: 訳注：この例だと実行時処理として扱われるような気がするのですが…

# 14. `virtual`属性が指定されていないデストラクタの使用

```cpp
class BaseWithNonvirtualDestructor {
public:
    void foo() {
        std::cout << "do foo\n";
    }

    virtual ~BaseWithNonvirtualDestructor() {
        std::cout << "called base destructor\n";
    }
};

class Derived : public BaseWithNonvirtualDestructor {
public:
    ~Derived() override {
        std::cout << "called derived destructor\n";
    }
};

void consume_base(std::unique_ptr<BaseWithNonvirtualDestructor> p) {
    p->foo();
    // deletes p when done
}

void base_with_nonvirtual_destructor() {
    auto derived = std::make_unique<Derived>();
    consume_base(std::move(derived));
}
```

基底クラス型にキャストしたポインタからクラスを破棄しようとした場合、基底クラスのデストラクタに`virtual`属性がないと派生クラスのデストラクタが呼び出されなくなってしまい、クラスが正常に破棄されない可能性がある。

# 15. メンバ変数の初期化順に対する誤解

```cpp
template<typename T>
class View {
public:
    View(T *start, std::size_t size) : m_start{start}, m_end{m_start + size} {
        std::cout << "view constructor: " << m_start << "," << m_end << '\n';
    }

private:
    T *m_start;
    T *m_end;
};

void class_member_initialization_order() {
    View<std::byte> v(nullptr, 1);
}
```

クラスのメンバ変数はコンストラクタ引数の順序ではなく、メンバの定義順に初期化される。

# 16. 未初期化の変数に対する参照

```cpp
void default_vs_value_initialization() {
    int x;
    int *x2 = new int;

    struct S {
        int n, m;
        std::string s;

        S() {
            std::cout << "constructor\n";
        }
    };

    S my_s;
    std::cout << my_s.n << " " << my_s.m << " \"" << my_s.s << "\"\n";

```

C、およびC++の変数はデフォルト初期化を実行した場合不定値を示すケースがあるので、初期値が必要な場合は明示的に初期化する必要がある。

原則として、変数は初期値を指定して定義した方が良い。

```cpp:修正例
    int y{};
    int *y2 = new int{};
    int *y3 = new int();

    S my_s2{};
    std::cout << my_s2.n << " " << my_s2.m << " \"" << my_s2.s << "\"\n";
}
```

# 17. マジックナンバーの過剰使用

```cpp
float energy(float m) {
    return m * 299792458.0 * 299792458.0;
}
```

参照先が定数の場合、コンパイラは自動的に最適化を行うので、数値リテラルはなるべく直接使用せずに`constexpr`変数などに格納した方が良い。


# 18. コンテナに対するイテレーションループ内での要素の追加・削除

```cpp
void modify_while_iterating() {
    std::vector<int> v{1, 2, 3, 4};

    for (auto x : v) {
        v.push_back(x);
    }


    for (auto it = v.begin(), end = v.end() ; it != end; ++it) {
        v.push_back(*it);
    }

    std::copy(v.begin(), v.end(), std::back_inserter(v));


    for (auto x: v) {
        std::cout << x;
    }
    std::cout << '\n';
}
```

イテレーションの最中にコンテナの要素を追加・削除するとイテレータの指示先が変化する可能性があるため、注意を払う必要がある。

# 19. 関数の戻り値に対する`std::move`の使用

```cpp
std::vector<int> make_vector(const int n) {
    std::vector<int> v{1, 2, 3, 4, 5};

    std::vector<int> w{1, 2};

    if (n == 0)
        return std::move(v);
    else
        return std::move(w);
}
```

関数の戻り値は自動的に右辺値を返すように最適化される。
手動で指定した場合、場合によっては最適化が抑制されてしまうので、避けたほうが良い。

# 20. `std::move`の挙動に対する誤解

以下は`std::move`の実装例である。

```cpp
template<typename T>
constexpr std::remove_reference_t<T> &&
move(T &&value) noexcept {
    return static_cast<std::remove_reference_t<T> &&>(value);
}
```

原則として`std::move`は変数に対する参照を右辺値型に`static_cast`するだけであり、メモリ間の移動は一切行わない。

# 21. 引数の評価順に依存した関数呼び出し

```cpp
void g(int a, int b, int c) {
    std::cout << a << ' ' << b << ' ' << c << '\n';
}

void function_evaluation_order_not_guaranteed() {
    std::string s = "but i have heard it works even if you don't believe in it";

    int i = 0;
    const auto inc_i = [&i]() -> int {
        return ++i;
    };

    g(inc_i(), inc_i(), inc_i());
}
```

C++において引数の評価順は規定されておらず、評価順によって結果が変わるコードの動作は未定義である。

# 22. 不必要なヒープアロケーションの実行

```cpp
struct Record {
    int id;
    std::string name;
};

void unnecessary_heap_allocations() {
    Record *customer = new Record{0, "James"};
    Record *other = new Record{1, "Someone"};

    // do work

    delete customer;
    delete other;
}
```

上記の例の場合、通常のスタックアロケーションで問題なく動作する。
`new`による初期化を使用する前に、ヒープアロケーションが本当に必要かどうかもう一度確認した方が良い。

# 23. スマートポインタの未使用


```cpp
struct Metadata {
    std::byte magic;
    std::size_t size;
    std::array<char, 20> name;
    std::array<char, 200> description;
    // other fields
};

Metadata *read_metadata(const std::ifstream &file) {
    auto *data = new Metadata{};

    // read fields from file

    return data;
}
```

変数を`new`を使用してアロケートした場合、例外処理などにより`delete`処理がスキップされた場合にメモリリークが発生してしまう。
あらかじめ`std::unique_ptr`などのスマートポインタにアドレス値を格納しておくことで、オブジェクトの消滅時にメモリが自動解放される。

# 24. 生成用ヘルパー関数を使用しないスマートポインタの初期化

```cpp
void f(std::unique_ptr<int> a, std::unique_ptr<float> b) {

}

void constructing_a_unique_ptr_directly() {
    f(std::unique_ptr<int>(new int{}),
      std::unique_ptr<float>(new float{}));
```

`std::make_unique`及び`std::make_shared`関数を使用することにより、コンストラクタ引数をforwardしつつ、オブジェクトの動的確保を自動化することができる。

```cpp:修正例
    f(std::make_unique<int>(),
      std::make_unique<float>());
```

# 25. コンテナ型定義以外での`new` `delete`構文の使用[^5]

```cpp
class Widget {
public:
    Widget() : meta{new Metadata{}} {
        // whatever
    }

    virtual ~Widget() {
        delete meta;
    }

private:
    Metadata *meta;
};
```

メモリ管理に問題がない使用法であっても、クラス側で独自に管理するのは単一責任の原則から見てふさわしくないので、できるだけスマートポインタを使用したほうが望ましい。

```cpp:修正例
class BetterWidget {
public:
    BetterWidget() : meta{std::make_unique<Metadata>()} {
        // whatever
    }

private:
    std::unique_ptr<Metadata> meta;
};
```

[^5]: 23とほとんど同じ内容のような気がするが気にしてはいけない

# 26. 手動リソース管理

```cpp
void read_from_a_file(char *name) {
    FILE *fp = fopen(name, "r");
    // ... work with file, EXCEPTION?
    fclose(fp);
```

`new`及び`delete`と同様、例外処理などによりリソースの解放漏れが発生するおそれがあるので、自動的にリソース管理できるオブジェクトがあればそちらを使用した方が良い。

```cpp:修正例
    std::ifstream input{name};
    // work with the file
```

このようなオブジェクトによる自動リソース管理をRAIIと呼び、例外安全なコードを書く為の推奨パターンとなっている。

ちなみに、`std::unique_ptr`及び`std::shared_ptr`にテンプレート引数として関数を渡すことで、カスタムデリータを指定することができる。


# 27. スマートポインタの不必要な使用

```cpp
std::shared_ptr<int> max(std::shared_ptr<int> a, std::shared_ptr<int> b) {
    return *a > *b ? std::move(a) : std::move(b);
}
```

`std::shared_ptr`の処理には実行時オーバーヘッドがあるため、引数などにおいて所有関係を管理する必要がない場合[^6]は`std::shared_ptr`を使用せず生ポインタを使用した方が良い。

```cpp:修正例
const int *max(const int *a, const int *b) {
    return *a > *b ? a : b;
}
```

[^6]: constポインタであるとは限らない。

# 28. `std::shared_ptr`の使い過ぎ

```cpp
struct Pizza {
    Pizza(float diameter, std::vector<std::string> toppings) :
            m_diameter{diameter}, m_toppings{std::move(toppings)} {}

    float m_diameter; // inches
    std::vector<std::string> m_toppings;
};

std::shared_ptr<Pizza> make_shared_pepperoni_pizza(float diameter) {
    std::vector<std::string> toppings = {"red sauce", "cheese", "pepperoni"};
    return std::make_shared<Pizza>(diameter, std::move(toppings));
}

std::unique_ptr<Pizza> make_unique_pepperoni_pizza(float diameter) {
    std::vector<std::string> toppings = {"red sauce", "cheese", "pepperoni"};
    return std::make_unique<Pizza>(diameter, std::move(toppings));
}

void convert_unique_to_shared_is_easy_and_cheap() {
    auto pizza = make_unique_pepperoni_pizza(16.0f);

    std::shared_ptr<Pizza> shared_pizza = std::move(pizza);
    std::shared_ptr<Pizza> shared_pizza2 = make_unique_pepperoni_pizza(16.0f);
}
```

前項と同様、参照カウンタが必要ない場合は`std::unique_ptr`を使用できないか検討した方が良い。
一時的に引数として利用するだけなら実体を参照渡しするか`get`メソッドを使用すればよく、必要であればほぼノーコストで`std::shared_ptr`に変換できる。

# 29. スレッド安全性が要求される場面での不適切な`std::shared_ptr`の使用

```cpp
struct Noisy {
    int x;

    Noisy() : x{0} {

    }

    ~Noisy() {
        std::osyncstream{std::cout} << "Deleted from thread " << std::this_thread::get_id() << '\n';
    }
};


void worker(std::shared_ptr<Noisy> noisy) {
    for (int i = 0; i < 5; ++i) {
        for (int j = 0; j < 10000; ++j) {
            noisy->x++;
        }
        std::osyncstream{std::cout} << "Thread " << std::this_thread::get_id() << " executing: " << noisy->x << "\n";
    }
}


void shared_ptr_is_NOT_threadsafe() {
    std::osyncstream{std::cout} << "Main thread " << std::this_thread::get_id() << " executing\n";

    auto noisy = std::make_shared<Noisy>();
    std::jthread t2(worker, noisy);
    std::jthread t1(worker, noisy);
    noisy.reset();
}
```

`std::shared_ptr`の参照カウンタはスレッドセーフであるものの、アドレスの参照にはスレッド安全性が保証されていないため、スレッド安全性が必要な場合は`std::lock_guard`などで制御する必要がある。

# 30. const型ポインタとconst変数を指すポインタの混同

```cpp
void const_pointer_vs_pointer_to_const() {
    int x = 0;
    int y = 0;

    const int *ptr_to_const = &x;
    int const *ptr_to_const2 = &x;
    // *ptr_to_const = 1; // NOT ALLOWED
    ptr_to_const = &y;
 
    int *const const_ptr = &x;
    *const_ptr = 1; // x == 1
    // const_ptr = &y; // NOT ALLOWED
}
```

`const int *`及び`int const *`型は`const int`型に対する通常のポインタなので、ポインタそのものに対しては`const`属性が適用されない。const型のポインタを定義する場合は`int * const`のように指定する必要がある。

# 31. （おまけ）コンパイラの警告無視

コード中に未定義動作が含まれている場合、コンパイラはwarningを生成するので、warningを無視すると重要な問題点を見逃してしまう恐れがある。

# Python版もあるよ！

[25 nooby Python habits you need to ditch - YouTube](https://www.youtube.com/embed/qUeud6DvOWI)

# 参考文献

- [C++20スマートポインタ入門](https://qiita.com/hmito/items/9b35a2438a8b8ee4b5af)