C++ コア ガイドライン:関数パラメータの in、out、in-out、consume、および forward の規則

関数パラメーターを渡すには多くの選択肢があります。値または参照で渡すことができます。参照は、const または non-const にすることができます。パラメータを移動または転送することもできます。あなたの決定は、それがインかどうかによって異なります 、そしてアウトインアウト消費 、またはフォワード 関数パラメーター。奇妙?投稿を読んでください!

ガイドラインに従って、in について話しましょう 、アウト出入り消費 、または進む パラメータ。

パラメータ受け渡し式のルール:

  • F.15:シンプルで従来型の情報伝達方法を好む
  • F.16:「in」パラメータについては、安価にコピーされた型を値で渡し、その他は 05 への参照で渡します
  • F.17:「in-out」パラメータの場合、非 11 への参照渡し
  • F.18:「消費」パラメータについては、25 で渡します と 37 パラメータ
  • F.19:「転送」パラメータの場合、40 で渡します 57のみ パラメータ
  • F.20:「出力」出力値については、出力パラメータよりも戻り値を優先してください
  • F.21:複数の「出力」値を返すには、タプルまたは構造体を返すことをお勧めします
  • F.60:60 を優先 77以上 「引数なし」が有効なオプションの場合

いろいろあると思いますが、ご了承ください。最初の規則 F.15 は、ガイドライン F.16 - F.21 をまとめたものです

F.15:シンプルで従来型の情報伝達方法を好む

これは、C++ コア ガイドラインの全体像です。これらは、通常のパラメータ受け渡し規則です。

これらのルールに基づいて、緑色で示されている、いわゆる高度なパラメーター受け渡しルールがいくつか追加されています。

ルールとそのバリエーションの根拠は、次のルールに従います。

F.16:「 in」パラメータで、安価にコピーされた型を値で渡し、その他は 88 への参照で渡します

in のこのルール パラメータは簡単で、例も同様です:

void f1(const string& s); // OK: pass by reference to const; always cheap

void f2(string s); // bad: potentially expensive

void f3(int x); // OK: Unbeatable

void f4(const int& x); // bad: overhead on access in f4()

セミナーでよく聞く質問:安くコピーできるとはどういう意味ですか?ガイドラインは非常に具体的です。

  • sizeof(p)> 4 * sizeof(int) の場合、パラメータ p をコピーしないでください
  • sizeof(p) <3 * sizeof(int) の場合、p への const 参照を使用しないでください

これらの数値は経験に基づいていると思います.

F.17:「in-out」パラメータについては、への参照渡し93 以外

インアウト パラメーターは関数内で変更されるため、const 以外の参照を使用することは理にかなっています。

void appendElements(std::vector<int>& vec){
 // append elements to vec
 ...
}

F.18:「消費」パラメータの場合、 101 で通過 と 110 パラメータ

これは、消費する最初の高度なルールです パラメーター。パラメータを使用して関数本体内に移動する場合は、右辺値参照を使用します。以下に例を示します:

void sink(vector<int>&& v) { // sink takes ownership of whatever the argument owned
 // usually there might be const accesses of v here
 store_somewhere(std::move(v));
 // usually no more use of v here; it is moved-from
}

この規則には例外があります。 std::unique_ptr は、移動が安価な移動専用型であるため、移動できます。

void sink(std::unique_ptr<int> p) { 
... }
...
sink(std::move(uniqPtr));

F.19:「フォワード」の場合パラメータ、129 で渡す 133のみ パラメータ

これは、std::make_unique や std::make_shared などのファクトリ メソッドが使用するイディオムです。どちらの関数も型 T と任意の数の引数 args と forward を取ります 変わらない T のコンストラクタに渡されます。こちらをご覧ください:

template<typename T, typename... Args> // 1
std::unique_ptr<T> make_unique(Args&&... args) // 2
{
 return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); // 3
}

このパターンは完全転送と呼ばれます:関数テンプレートが左辺値または右辺値の特性を変更せずに引数を転送する場合、それを完全転送と呼びます。

完璧な転送に関する私の以前の投稿は次のとおりです。

関数テンプレートの完全な転送を行うには、3 つのステップからなるレシピに従う必要があります。 std::make_unique のような可変個引数テンプレート (...) であってはならないので、この部分はスキップします。

<オール>
  • テンプレート パラメータが必要です:typename Args
  • 転送参照ごとに関数の引数を取ります:Args&&args
  • 関数の引数を転送する:std::forward(args)
  • F.20:「出力」出力値の場合、出力よりも戻り値を優先するパラメータ

    明示的な戻り値は、関数の意図を文書化します。 out として参照するパラメータを使用する 出力値は誤解を招く可能性があります。これはインアウト値でもかまいません。関数の結果を値で返すことは、ムーブ セマンティックを暗黙的に使用する標準コンテナーにも当てはまります。

    // OK: return pointers to elements with the value x
    vector<const int*> find_all(const vector<int>&, int x);
    
    // Bad: place pointers to elements with value x in-out
    void find_all(const vector<int>&, vector<const int*>& out, int x);
    

    この規則には例外があります。移動にコストがかかるオブジェクトがある場合は、out パラメータとして参照を使用できます。

    struct Package { // exceptional case: expensive-to-move object
     char header[16];
     char load[2024 - 16];
    };
    
    Package fill(); // Bad: large return value
    void fill(Package&); // OK
    

    F.21:複数の「出力」値を返すには、タプルまたは構造体

    関数が複数の out を返す場合があります 価値。この場合、std::tuple または struct を使用する必要がありますが、参照でパラメーターを使用しないでください。これは非常にエラーが発生しやすいです。

    // BAD: output-only parameter documented in a comment
    int f(const string& input, /*output only*/ string& output_data)
    {
     // ...
     output_data = something();
     return status;
    }
    
    // GOOD: self-documenting
    tuple<int, string> f(const string& input)
    {
     // ...
     return make_tuple(status, something());
    }
    

    C++17 と複数の値を返す構造化バインディングを使用すると、非常に便利になります。

    auto [value, success] = getValue(key);
    
    if (success){
     // do something with the value;
    

    関数 getValue はペアを返します。 success は、キーのクエリが成功したかどうかを示します。

    次のルールは特別です。私にとって、このルールはよりセマンティックなルールです。とにかく。

    F.60:142 156以上 「引数なし」が有効なオプションの場合

    パラメータが nullptr などの「引数なし」を取得できない場合は、T&を使用する必要があります。 T&を nullptr にすることはできません。 nullptr が可能な場合は、T* を使用してください。

    std::string upperString(std::string* str){
     if (str == nullptr) return std::string{}; // check for nullptr
     else{
     ...
    }
    

    オプションの引数がない場合は、確認する必要があります。

    次のステップ

    この投稿は約 でした 、アウト出入り消費進む パラメーターですが、答えるべき質問は他にもあります。シーケンスまたは所有権をどのように処理する必要がありますか?それについては次の投稿で書きます。