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


604

私は理由が合格することが示唆されたハーブサッターによる最近の話を聞いたstd::vectorstd::stringすることによりconst &大幅になくなっているし。彼は次のような関数を書くことが今や望ましいと提案しました:

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

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

ハーブがなぜこれを言ったのか誰もが説明できますか?


89
この質問に対する最良の答えは、おそらくC ++ Nextで Dave Abrahamsの記事を読むことだと思います。これについては、トピック外または建設的でないとみなされるものについては何も見ていません。それは事実についての答えがあるプログラミングに関する明確な質問です。
Jerry Coffin

魅力的なので、とにかくコピーを作成する必要がある場合、値渡しは参照渡しよりも高速である可能性があります。
ベンジ

3
@Sz。質問が誤って重複として分類され、クローズされることに敏感です。私はこの事件の詳細を覚えておらず、それらを再検討していません。代わりに、私が間違えたという前提でコメントを削除します。これを私の注意を喚起してくれてありがとう。
Howard Hinnant

2
@HowardHinnantさん、ありがとうございました。このレベルの注意力と感性に出会うのは常に貴重な瞬間です。とても爽やかです!(もちろん、私の場合は削除します。)
Sz。

回答:


393

ハーブが彼の言ったことを言った理由はこのようなケースのためです。

function Aを呼び出すfunction B、function を呼び出すfunction があるとしますC。そしてAを通じて文字列を渡しBとにCA知らないか気にしないC; すべてAについて知っていることですB。つまりC、の実装の詳細ですB

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取り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を使用した小さな文字列には使用できません。やる価値はありますか?

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


2
値への移動は参照を使用するよりもコストが高いと言うとき、それは一定の量(移動される文字列の長さに関係なく)だけ高くなりますよね?
Neil G

3
@NeilG:「実装依存」の意味を理解していますか?SSOが実装されているかどうかと実装方法に依存しているため、あなたの言っていることが間違っています。
ildjarn 2012

17
@ildjarn:分析のために、何かの最悪のケースが定数に拘束されている場合、それはまだ一定の時間です。最長の小さな文字列はありませんか?その文字列は、コピーするのに一定の時間がかかりませんか?小さい文字列はすべて、コピーにかかる時間が短くないですか?次に、小さな文字列の文字列コピーは順序分析で「一定の時間」になります。小さな文字列のコピーにはさまざまな時間がかかります。次数分析は漸近的な振る舞いに関係しています
Neil G

8
@NeilG:確かに、しかし、あなたのの質問は「でした(移動された文字列の長さとは無関係に)一定の量で、まだ右より高価だと?」私はメイクにしようとしている点であり、それはによって、より高価になる可能性が異なります文字列の長さに応じた一定量。これは「no」として合計されます。
ildjarn 2012

13
moved値によるケースで文字列がBからCになるのはなぜですか?BであればB(std::string b)、CがあるC(std::string c)のいずれか、我々は呼び出す必要がC(std::move(b))Bまたはb終了するまで(これ「から不動」)変わらないしなければなりませんB。(おそらく、b呼び出し後に使用されない場合、最適化コンパイラーはas-ifルールで文字列を移動しますが、強力な保証はないと思います。)strto のコピーにも同じことが当てはまりますm_str。関数パラメーターが右辺値で初期化された場合でも、それは関数内の左辺値であり、std::moveその左辺値から移動する必要があります。
Pixelchemist 2015

163

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

いいえ。多くの人々は、それが適用されるドメインを超えて(デイブ・エイブラハムズを含む)は、このアドバイスを取る、とに適用することを簡素化し、すべて std::stringのパラメータ- 常に渡すstd::string値によっては、任意およびすべての任意のパラメータとアプリケーションの最適化これらの理由のための「ベストプラクティス」ではありません話し合い/記事が焦点を当てているのは、限られた一連のケースにのみ適用されます。

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

いつものように、あなたがコピーを必要としないとき、const参照によって渡すことは多くのコピーを節約します

次に、具体的な例を示します。

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

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

const参照によって渡される日は終わっていません-ルールはかつてよりも複雑でした。パフォーマンスが重要な場合は、実装で使用する詳細に基づいて、これらの型を渡す方法を検討することをお勧めします。


3
スタックを使用する場合、通常のABIは、スタックを使用せずにレジスタ内の単一の参照を渡します。
ahcox

63

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

ただし、使用するものによっても異なります。

次の機能を考えてみましょう:

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秒

4
より適切なのは、関数内で何が起こっいるかです:参照付きで呼び出された場合、値で渡すときに省略できるコピーを内部で作成する必要がありますか?
-leftaroundabout

1
@leftaroundaboutはい、もちろんです。両方の関数がまったく同じことをしているという私の仮定。
BЈовић

5
それは私の趣旨ではありません。値渡しと参照渡しのどちらが良いかは、関数内で何をしているのかによって異なります。あなたの例では、実際には文字列オブジェクトの多くを使用していないため、参照の方が明らかに優れています。ただし、関数のタスクが文字列を構造体に配置することや、たとえば文字列の複数の分割を含む再帰的アルゴリズムを実行することである場合、値で渡すと、参照で渡すよりも実際にコピーを節約できます。ニコル・ボーラスはそれをかなりよく説明しています。
leftaroundabout

3
私にとって「関数内で何をするかによる」というのは悪い設計です。なぜなら、関数のシグネチャは実装の内部に基づいているからです。
Hans Olsson 2016

1
タイプミスかもしれませんが、最後の2つの文字タイミングはループが10倍少なくなっています。
TankorSmash 2017

54

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

  • 文字列を変更しない場合(扱いは読み取り専用)、として渡し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;
}

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

関数本体で引数をどのように使用するかを認識する必要があるだけです。読み取り専用か否か、およびスコープ内にとどまるかどうか。


2
場合によっては参照渡しをお勧めしますが、常に値渡しを推奨するガイドラインを示しています。
キース・トンプソン

3
@KeithThompson 関数の引数をコピーしないでください。const ref&を変更するために内部変数にをコピーしないことを意味します。変更する必要がある場合...パラメータを値にします。英語を話さない自分にとっては、かなりはっきりしています。
CodeAngry 2013

5
@KeithThompson ガイドラインの引用(関数の引数をコピーしないでください。代わりに値で渡して、コンパイラーにコピーを任せてください。はそのページからコピーされます。それが十分に明確でない場合、私は助けることができません。最良の選択をするためにコンパイラーを完全に信頼しません。関数の引数を定義する方法については、自分の意図をかなり明確にしたいと思います。#1読み取り専用の場合はconst ref&です。#2記述する必要がある場合、または範囲外になることがわかっている場合...値を使用します。#3元の値を変更する必要がある場合は、を渡しref&ます。#4 pointers *引数がオプションの場合に使用できるので使用できnullptrます。
CodeAngry 2013

11
値渡しと参照渡しのどちらを行うかという問題については、私はどちらの立場も取っていません。私の要点は、場合によっては参照渡しを提唱しているが、常に値渡しを推奨するガイドラインを(自分の立場を支持するように)引用していることです。ガイドラインに同意しない場合は、そのように言い、理由を説明することをお勧めします。(cpp-next.comへのリンクが機能しません。)
キース・トンプソン

4
@キーストンプソン:あなたはガイドラインを言い換えているのです。「常に」値渡しすることではありません。要約すると、「ローカルコピーを作成する場合は、値渡しを使用して、コンパイラにコピーを実行させる」でした。コピーを作成するつもりがなかったときに値渡しを使用すると言っているのではありません。
Ben Voigt 2015

43

あなたが実際にコピーを必要としない限り、それを取ることはまだ合理的const &です。例えば:

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

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

コピー必要な場合は、通常、値による受け渡しと戻りが(常に?)最良のオプションです。実際、C ++ 03では、余分なコピーが実際にパフォーマンスの問題を引き起こしていることに気付かない限り、私は一般にそれについて心配しません。コピー省略は、最近のコンパイラではかなり信頼できるようです。RVOのコンパイラサポートの表を確認する必要があるという人々の懐疑論と主張は、今日ではほとんど使用されていないと思います。


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


2
通常、移動コンストラクターはで実装されますがnoexcept、コピーコンストラクターは明らかに実装されていません。
-leftaroundabout

25

ほとんど。

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

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

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

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

を使用するとstd::string_view、Cスタイルで'\0'終了する文字バッファーを想定しているAPIに上記の文字列を渡さない限り、std::string割り当てを危険にさらすことなく、より効率的に機能のような機能を取得できます。未加工のC文字列はstd::string_view、割り当てや文字のコピーなしでに変換することもできます。

その時点での使用方法std::string const&は、データを大量にコピーせず、nullで終了するバッファーを予期するCスタイルのAPIに渡し、std::string提供するより高レベルの文字列関数が必要な場合です。実際には、これはまれな要件のセットです。


2
私はこの回答に感謝しますが、ドメイン固有のバイアスの影響を受けている(多くの質の高い回答と同様)ことを指摘したいと思います。つまり、「実際には、これはまれな要件のセットです」…私自身の開発経験では、これらの制約(著者には異常に狭いと思われる)は、文字通り常に満たされています。これを指摘する価値があります。
fish2000 '06 .06.18

1
@ fish2000明確にするためにstd::string、支配するには、これらの要件の一部だけでなく、すべてが必要です。そのうちの1つまたは2つでもよくあると思います。たぶん、通常3つすべてが必要です(
たとえば

@ Yakk-AdamNevraumontこれはYMMVのことですが、POSIXや(C)文字列のセマンティクスが最も一般的であるような他のAPIに対して(たとえば)プログラミングしている場合は、頻繁に使用されます。私が本当に好きだと言っておくべきstd::string_viewです。ご指摘のとおり、「生のC文字列は、割り当てや文字のコピーなしでstd :: string_viewに変換することもできます」。実際、そのようなAPIの使用法です。
fish2000 2018

1
@ fish2000「「生のC文字列は、割り当てや文字のコピーをせずにstd :: string_viewにすることもできます」これは覚えておく価値のあることです。確かに、しかしそれは最良の部分を省略します-生の文字列が文字列リテラルの場合、ランタイムのstrlen()さえ必要としません
ドンハッチ

17

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

これが推奨される理由invalは、が引数式から構築されるため、常に適切に移動またはコピーされるためです。引数の所有権が必要であると想定すると、パフォーマンスが低下することはありません。そうでない場合constでも、参照はより良い方法です。


2
コピーコンストラクターがSSOを使用していなくても心配する必要がないほどスマートであるという興味深い点。おそらく正しいです。それが本当かどうかを確認する必要があります;-)
Benj

3
@Benj:私が知っている古いコメントですが、SSOが十分に小さい場合、無条件にそれをコピーすることは、条件付きブランチを行うよりも高速です。たとえば、64バイトはキャッシュラインであり、非常に簡単な時間でコピーできます。x86_64ではおそらく8サイクル以下です。
Zan Lynx 2013

SSOがコピーコンストラクターによってコピーされない場合でもstd::string<>、スタックから32バイトが割り当てられ、そのうちの16バイトを初期化する必要があります。これを参照用に割り当てて初期化したわずか8バイトと比較してください。これは、CPU作業の2倍の量であり、他のデータで使用できないキャッシュ領域の4倍を占めます。
cmaster-モニカを

ああ、私はレジスタに関数の引数を渡すことについて話すのを忘れていました。これにより、最後の呼び出し先の参照のスタック使用量がゼロになります...
cmaster-モニカを

16

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

以下は、何が要求されているかを測定するコードです。

#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つだけ必要ですが、左辺値とx値を渡すときに追加のmove構造が必要です。これは、特定の状況で受け入れられる場合と受け入れられない場合があります。どちらのソリューションにも利点と欠点があります。


1
std :: stringは標準ライブラリクラスです。すでに移動可能であり、コピー可能です。これがどのように関連しているかはわかりません。OPは、移動とコピーのパフォーマンスではなく、移動と参照のパフォーマンスについて詳しく尋ねています。
Nicol Bolas

3
この答えは、HerbとDaveの両方によって記述された値渡し設計の下で、std :: stringが受ける移動とコピーの数を数えます。私はデモでOPのコードを使用していますが、コピー/移動されているときにダミーの文字列で置き換えて叫ぶことを除きます。
ハワードHinnant

テストを実行する前に、コードを最適化する必要があります…
常磁性クロワッサン

3
@TheParamagneticCroissant:異なる結果が得られましたか?もしそうなら、どのコンパイラをどのコマンドライン引数で使用しますか?
Howard Hinnant、2014

14

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ます。

以下のコードは、マイナー効率オプションと共に、落とし穴と回避策を示す-とオーバーロードconst char*方法に記載されるようにされているC ++における基準として文字列リテラルを渡すが方法を

(注: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

これは別の問題であり、WidgetBadRefはconst&パラメータを持つ必要はありません。問題は、WidgetSafeCopyが文字列パラメーターを受け取っただけの場合、遅くなるかどうかです。(メンバーへの一時的なコピーの方が確かに見つけやすいと思います)
Superfly Jon

これT&&は必ずしも普遍的な参照ではないことに注意してください。実際、std::string&&型の演繹が実行されないためは常に右辺値参照であり、普遍的な参照になることはありません。したがって、Stroustroup&SutterのアドバイスはMeyersのアドバイスと矛盾しません。
ジャスティンタイム-モニカ

@JustinTime:ありがとうございます。std :: string &&が普遍的な参照になると実際に主張している誤った最終文を削除しました。
circlepi314

@ circlepi314どういたしまして これは簡単にT&&取り違えるものであり、推定された汎用参照か、推定されていない右辺値参照かを混乱させることがあります。彼らが普遍的な参照のために異なる記号を導入した場合はおそらくより明確になります(&&&との組み合わせとして&など&&)、しかしそれはおそらくばかげて見えるでしょう。
ジャスティン時間-モニカを

8

のC ++リファレンスを使用したIMO std::stringは迅速で短いローカル最適化ですが、値による受け渡しを使用する方がグローバルな最適化である場合とそうでない場合があります。

だから答えは:それは状況に依存します:

  1. すべてのコードを外部から内部の関数に書き込む場合、コードの機能がわかっている場合は、参照を使用できますconst std::string &
  2. ライブラリコードを記述したり、文字列が渡されるライブラリコードを多用したりする場合は、std::stringコピーコンストラクタの動作を信頼することで、グローバルな意味でより多くを得ることができます。

6

「Herb Sutter "Back to the Basics!Essentials of Modern C ++ Style"」を参照してください。他のトピックの中で、彼は過去に与えられたパラメーター受け渡しのアドバイス、およびC ++ 11に付属する新しいアイデアをレビューし、特に文字列を値で渡すというアイデア。

スライド24

ベンチマークstd::stringは、関数がそれをとにかくコピーする場合、値によるsの受け渡しが大幅に遅くなる可能性があることを示しています!

これは、常に完全なコピーを作成し(その後、所定の場所に移動する)、const&バージョンが古い文字列を更新するように強制しているためです。

スライド27を参照してください。「設定」機能の場合、オプション1は以前と同じです。オプション2は右辺値参照のオーバーロードを追加しますが、これにより、複数のパラメーターがある場合に組み合わせが爆発します。

値渡しのトリックが有効なのは、文字列を作成する必要がある(既存の値を変更しない)「シンク」パラメーターの場合のみです。つまり、パラメーターが一致する型のメンバーを直接初期化するコンストラクターです。

あなたはこの心配をして行くことができますどのように深く、見見たい場合はニコライJosuttis氏の(それとプレゼンテーションと幸運を「 -完了!パーフェクト」をn回前のバージョンで不具合を発見した後エヴァーがありました。?)


これは、標準ガイドラインの⧺F.15にも要約されています。


3

@JDługoszがコメントで指摘しているように、ハーブは別の(後で?)講演で他のアドバイスを提供します。おおよそここからhttps://youtu.be/xnqTKD8uD64?t=54m50sを参照してください

彼のアドバイスはf、これらのシンク引数から構成要素を移動すると仮定して、いわゆるシンク引数を取る関数の値パラメーターのみを使用することになります。

この一般的なアプローチでは、左辺値と右辺値の引数にf合わせて調整された最適な実装と比較して、左辺値と右辺値の引数の両方に移動コンストラクタのオーバーヘッドが追加されるだけです。これが当てはまる理由を確認するためにf、値のパラメーターを取るとしますT

void f(T x) {
  T y{std::move(x)};
}

flvalue引数を指定して呼び出すと、コピーコンストラクターが呼び出されて構成されx、移動コンストラクターが呼び出されて構成されyます。一方、f右辺値引数を指定して呼び出すと、moveコンストラクターが呼び出され、constructが呼び出されx、別のmoveコンストラクターが呼び出されて、constructが呼び出されyます。

一般に、flvalue引数の最適な実装は次のとおりです。

void f(const T& x) {
  T y{x};
}

この場合、を構築するために1つのコピーコンストラクターのみが呼び出されyます。ffor rvalue引数の最適な実装は、再び一般的に次のようになります。

void f(T&& x) {
  T y{std::move(x)};
}

この場合、を構築するために1つの移動コンストラクタのみが呼び出されyます。

したがって、賢明な妥協案は、値パラメーターを取り、最適な実装に関してlvalueまたはrvalue引数のいずれか1つの追加moveコンストラクター呼び出しを持つことです。これは、Herbの講演で与えられたアドバイスでもあります。

@JDługoszがコメントで指摘したように、値による受け渡しは、sink引数からオブジェクトを構築する関数に対してのみ意味があります。f引数をコピーする関数がある場合、値渡しのアプローチは、一般的な定数渡しのアプローチよりもオーバーヘッドが大きくなります。fパラメータのコピーを保持する関数の値渡しアプローチは、次の形式になります。

void f(T x) {
  T y{...};
  ...
  y = std::move(x);
}

この場合、左辺値引数にはコピー構築と移動割り当てがあり、右辺値引数には移動構築と移動割り当てがあります。左辺値引数の最も最適なケースは次のとおりです。

void f(const T& x) {
  T y{...};
  ...
  y = x;
}

これは、割り当てのみに要約されます。これは、値渡しアプローチに必要なコピーコンストラクターと移動割り当てよりもはるかに安価になる可能性があります。この理由は、割り当てはで既存の割り当てられたメモリを再利用する可能性がyあり、そのため(割り当て解除)ができないのに対し、コピーコンストラクターは通常メモリを割り当てるからです。

右辺値引数の場合f、コピーを保持するための最適な実装は次の形式です。

void f(T&& x) {
  T y{...};
  ...
  y = std::move(x);
}

したがって、この場合は移動割り当てのみです。右辺値をそのバージョンに渡すと、fconst参照が取得され、移動割り当てではなく割り当てのみがかかります。相対的に言えば、fこの場合、一般的な実装としてconst参照を使用するバージョンが望ましいです。

したがって、一般的に、最適な実装を行うには、トークで示したように、オーバーロードするか、ある種の完全な転送を行う必要があります。欠点はf、引数の値カテゴリをオーバーロードすることを選択した場合のパラメーターの数に応じて、必要なオーバーロードの数が増えることです。完全な転送には、fテンプレート関数になり、仮想化を妨げるという欠点があり、100%正しくしたい場合は、コードが大幅に複雑になります(詳細については、トークを参照してください)。


私の新しい回答でハーブサッターの発見を参照してください。構成を移動するときにのみ実行し、割り当てを移動しないでください。
JDługosz

1
@JDługosz、ハーブの話へのポインターのおかげで、私はそれを見て、私の答えを完全に修正しました。(移動)割り当てのアドバイスを知りませんでした。
Ton van den Heuvel、2018年

その図とアドバイスは、現在、標準ガイドライン文書にあります。
JDługosz

1

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

この言語の問題を周りにハックするためには、STLは可能性があなたの例では「C()は、」移動-セマンティックコピーを作成することができますとにかく、そして忠実に参照カウント(可変)に関して「ポーラ」無視します。明確に指定されている限り、これで問題ありません。

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

ここでC ++は「派生クラスconstの細分性」を提供しないため、優れた仕様を記述し、光沢のある新しい「const可動文字列」(cmstring)オブジェクトを作成することが、私が見た中で最高のソリューションです。


@BenVoigtええと...キャストするのではなく、変更可能にする必要があります...しかし、派生クラスでSTLメンバーを変更可能に変更することはできません。
Erik Aronesty、2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.