652

Herb Sutter の最近の講演を聞きました。彼は、通過する理由と通過する理由はほとんどなくなっていると示唆しましたstd::vector。彼は、次のような関数を書くことが今では好ましいと提案しました:std::stringconst &

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

return_val関数が戻る時点で は右辺値になるため、非常に安価な移動セマンティクスを使用して返すことができることを理解しています。ただし、inval参照のサイズよりもはるかに大きい (通常はポインターとして実装されます)。これは、ヒープへのポインターや短い文字列の最適化のためstd::stringのメンバーなど、さまざまなコンポーネントがあるためです。char[]したがって、参照渡しはまだ良い考えだと思います。

なぜハーブがそう言ったのか説明できる人はいますか?

4

13 に答える 13

422

ハーブがこんなことを言ったのは、こういう場合だからだ。

functionAを呼び出す関数があり、 function をB呼び出すとしますC。とをA介して文字列を渡します。知らない、または気にしない; すべてが知っていることです。つまり、 の実装の詳細です。BCACABCB

A が次のように定義されているとします。

void A()
{
  B("value");
}

B と C が で文字列を取る場合const&、次のようになります。

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

すべて順調です。ポインターを渡すだけで、コピーも移動もせず、みんな幸せです。文字列を保存しないためC、 a を取ります。const&それは単にそれを使用します。

ここで、簡単な変更を 1 つ加えたいと思いCます。文字列をどこかに保存する必要があります。

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

こんにちは、コンストラクターと潜在的なメモリ割り当てをコピーします ( Short String Optimization (SSO)は無視してください)。C++11 のムーブ セマンティクスは、不要なコピー構築を削除できるはずですよね? そしてA一時的に渡します。データをコピーCしなければならない理由はありません。与えられたものを放棄するだけです。

できないことを除いて。かかるからconst&です。

Cパラメータを値で取得するように変更すると、そのパラメータBへのコピーが行われるだけです。私は何も得ません。

したがって、データをシャッフルすることstrに依存して、すべての関数を値渡ししていれば、この問題は発生しません。std::move誰かがそれを保持したい場合は、保持できます。そうでない場合は、まあ。

それはより高価ですか?はい; 値への移動は、参照を使用するよりもコストがかかります。コピペより安いの?SSO を使用する小さな文字列には適していません。やる価値はありますか?

ユースケースによって異なります。メモリ割り当てがどれくらい嫌いですか?

于 2012-04-19T16:41:47.903 に答える
174

const std :: string&をパラメータとして渡す日は終わりましたか?

いいえ多くの人がこのアドバイス(Dave Abrahamsを含む)を適用するドメインを超えて、すべての std::stringパラメーターに適用するように単純化します-これらの最適化により、常に値を渡すstd::stringことは、すべての任意のパラメーターとアプリケーションの「ベストプラクティス」ではありません講演/記事は、制限された一連のケースにのみ適用されることに焦点を当てています。

値を返す場合、パラメーターを変更する場合、または値を取得する場合は、値を渡すことでコストのかかるコピーを節約し、構文上の利便性を提供できます。

相変わらず、const参照を渡すと、コピーが不要な場合にコピーを大幅に節約できます。

次に、特定の例を示します。

ただし、invalは、参照のサイズ(通常はポインターとして実装されます)よりもかなり大きくなります。これは、std :: stringには、ヒープへのポインタや短い文字列を最適化するためのメンバーchar[]などのさまざまなコンポーネントがあるためです。ですから、参照で渡すことはまだ良い考えであるように私には思えます。ハーブがこれを言った理由を誰かが説明できますか?

スタックサイズが懸念される場合(そしてこれがインライン化/最適化されていない場合)、return_val+ inval> return_val-IOW、ここで値を渡すことにより、ピークスタック使用量を減らすことができます(注:ABIの過度の単純化)。一方、const参照を渡すと、最適化が無効になる可能性があります。ここでの主な理由は、スタックの増加を回避することではなく、適用可能な場所で最適化を実行できるようにすることです。

const参照を通過する時代は終わりではありません。ルールは、以前よりも複雑になっています。パフォーマンスが重要な場合は、実装で使用する詳細に基づいて、これらのタイプを渡す方法を検討するのが賢明です。

于 2012-04-19T21:07:49.760 に答える
67

これは、コンパイラの実装に大きく依存します。

ただし、使用するものにもよります。

次の関数を考えてみましょう:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

これらの関数は、インライン化を避けるために別のコンパイル ユニットで実装されます。次に:
1. これら 2 つの関数にリテラルを渡すと、パフォーマンスに大きな違いは見られません。どちらの場合も、文字列オブジェクトを作成する必要があり
ます 2. 別の std::string オブジェクトを渡すと、 がディープ コピーを実行するため、foo2パフォーマンスが向上します。foo1foo1

私の PC では、g++ 4.6.1 を使用して、次の結果が得られました。

  • 参照による変数: 1000000000 反復 -> 経過時間: 2.25912 秒
  • 値による変数: 1000000000 反復 -> 経過時間: 27.2259 秒
  • 参照によるリテラル: 100000000 反復 -> 経過時間: 9.10319 秒
  • 値によるリテラル: 100000000 反復 -> 経過時間: 8.62659 秒
于 2012-04-19T15:44:04.517 に答える
64

短い答え:いいえ! 長い答え:

  • 文字列を変更しない場合 (読み取り専用として扱います)、として渡しますconst ref&
    (const ref&明らかに、それを使用する関数が実行されている間、スコープ内にとどまる必要があります)
  • 変更する予定がある場合、またはスコープ(スレッド)から外れることがわかっている場合は、 として渡し、関数本体内に をvalueコピーしないでください。const ref&

cpp-next.comに「速度が欲しい、値で渡す!」という投稿がありました。. TL;DR:

ガイドライン: 関数の引数をコピーしないでください。代わりに、それらを値で渡し、コンパイラーにコピーさせます。

^ の翻訳

関数の引数をコピーしないでください--- つまり、内部変数にコピーして引数の値を変更する場合は、代わりに値の引数を使用してください

だから、これをしないでください:

std::string function(const std::string& aString){
    auto vString(aString);
    vString.clear();
    return vString;
}

これを行います

std::string function(std::string aString){
    aString.clear();
    return aString;
}

関数本体の引数値を変更する必要がある場合。

関数本体で引数をどのように使用する予定かを知っておく必要があります。読み取り専用かそうでないか...そして範囲内に収まるかどうか。

于 2013-08-23T16:33:00.330 に答える
44

実際にコピーが必要な場合を除き、const &. 例えば:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

これを変更して文字列を値で取得すると、パラメータを移動またはコピーすることになりますが、その必要はありません。コピー/移動はコストが高くなる可能性が高いだけでなく、新たな潜在的な障害が発生する可能性もあります。コピー/移動は例外をスローする可能性があります (たとえば、コピー中の割り当てが失敗する可能性があります) が、既存の値への参照はできません。

コピーが必要な場合は、値による受け渡しが通常 (常に?) 最良のオプションです。実際、C++03 では、余分なコピーが実際にパフォーマンスの問題を引き起こすことが判明しない限り、通常は気にしません。コピー省略は、最新のコンパイラではかなり信頼できるようです。RVO のコンパイラ サポートのテーブルを確認する必要があるという人々の懐疑論や主張は、今日ではほとんど時代遅れになっていると思います。


要するに、C++11 はこの点に関して、コピー省略を信頼していない人々を除いて、実際には何も変更していません。

于 2012-04-19T15:29:34.680 に答える
31

ほとんど。

C++17 には がありますbasic_string_view<?>。これにより、基本的にstd::string const&パラメーターの 1 つの狭いユース ケースに絞り込まれます。

移動セマンティクスの存在により、1 つのユース ケースが排除さstd::string const&れました。パラメーターを格納する予定がある場合は、パラメーターから取得std::stringできるため、by 値を使用する方が最適ですmove

誰かが生の C で関数を呼び出した場合、この場合は 2"string"つではなく、1 つのstd::stringバッファーのみが割り当てられることを意味しstd::string const&ます。

ただし、コピーを作成するつもりがない場合std::string const&でも、C++14 で取得することは役に立ちます。

を使用すると、上記の文字列を C スタイルの終了文字バッファーstd::string_viewを予期する API に渡さない限り、割り当ての危険を冒すことなく、同様の機能をより効率的に取得できます。生の C 文字列は、割り当てや文字のコピーを行わずに に変換することもできます。'\0'std::stringstd::string_view

その時点での用途std::string const&は、データを大量にコピーするのではなく、null で終了するバッファーを期待する C スタイルの API にデータを渡す予定であり、std::string提供される高レベルの文字列関数が必要な場合です。実際には、これはまれな一連の要件です。

于 2015-01-26T03:09:35.937 に答える
18

std::stringPlain Old Data(POD)ではなく、その生のサイズはこれまでで最も関連性の高いものではありません。たとえば、SSO の長さを超えてヒープに割り当てられた文字列を渡す場合、コピー コンストラクターは SSO ストレージをコピーしないと予想されます。

これが推奨される理由invalは、 が引数式から構築され、常に必要に応じて移動またはコピーされるためです。引数の所有権が必要であると仮定すると、パフォーマンスの低下はありません。そうでない場合constでも、リファレンスの方が適している可能性があります。

于 2012-04-19T15:24:01.737 に答える
17

この質問の回答をここにコピーして貼り付け、この質問に合うように名前とスペルを変更しました。

何が求められているかを測定するコードは次のとおりです。

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

私にとって、これは次の出力を出力します:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

以下の表は私の結果をまとめたものです (clang -std=c++11 を使用)。最初の数字はコピー構築の数で、2 番目の数字は移動構築の数です。

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

値渡しソリューションではオーバーロードが 1 つしか必要ありませんが、lvalue と xvalue を渡すときに追加の move 構築が必要になります。これは、特定の状況で受け入れられる場合と受け入れられない場合があります。どちらのソリューションにも長所と短所があります。

于 2012-04-19T16:13:27.920 に答える
15

Herb Sutter は、Bjarne Stroustroup とともにconst std::string&、パラメータ タイプとして推奨することについて、まだ記録に残っています。https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-inを参照してください。

ここでの他の回答のいずれにも記載されていない落とし穴があります。文字列リテラルをconst std::string&パラメーターに渡すと、リテラルの文字を保持するためにオンザフライで作成された一時的な文字列への参照が渡されます。その参照を保存すると、一時文字列の割り当てが解除されると無効になります。安全のために、参照ではなくコピーを保存する必要があります。この問題は、文字列リテラルがconst char[N]型であり、 への昇格が必要であるという事実から生じstd::stringます。

以下のコードは、 Is there a way to pass a stringliteral as reference in C++ でconst char*説明されているように、マイナーな効率オプション (メソッドによるオーバーロード) とともに、落とし穴と回避策を示しています。

(注: Sutter と Stroustroup は、文字列のコピーを保持する場合は、&& パラメーターと std::move() を使用してオーバーロードされた関数も提供することをお勧めします。)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

出力:

const char * constructor
const std::string& constructor

Second string
Second string
于 2016-01-27T15:50:57.590 に答える
8

C++リファレンスを使用するIMOstd::stringは、迅速かつ短いローカル最適化ですが、値渡しを使用すると、より優れたグローバル最適化になる可能性があります(またはそうでない場合もあります)。

したがって、答えは次のとおりです。状況によって異なります。

  1. 外側から内側の関数まですべてのコードを記述した場合、コードが何をするかがわかり、参照を使用できますconst std::string &
  2. ライブラリ コードを記述したり、文字列が渡されるライブラリ コードを頻繁に使用したりする場合は、std::stringコピー コンストラクターの動作を信頼することで、グローバルな意味でより多くのことを得ることができます。
于 2012-04-25T09:26:34.833 に答える
0

問題は、「const」が非粒状修飾子であることです。通常、「const string ref」が意味するのは、「参照カウントを変更しない」ではなく、「この文字列を変更しない」ことです。C++ では、どのメンバーが "const" であるかを言う方法はまったくありません。それらはすべてそうであるか、どれもそうではありません。

この言語の問題を回避するために、STL例の "C()" がムーブ セマンティック コピーを作成できるようにし、参照カウント (変更可能) に関して "const" を忠実に無視することができますそれが適切に指定されている限り、これは問題ありません。

STL はそうではないので、私は const_casts<> 参照カウンターを取り除くバージョンの文字列を持っています (クラス階層で何かをさかのぼって変更可能にする方法はありません)。そして、漏れや問題なく、一日中、深い機能でそれらのコピーを作成します。

ここで C++ は「派生クラス const 粒度」を提供しないため、適切な仕様を作成し、光沢のある新しい「const可動文字列」(cmstring) オブジェクトを作成することが、私が見た中で最良の解決策です。

于 2015-02-20T14:36:49.897 に答える