17

C++を初めて使用する人を知っているとしましょう。彼はポインターを渡しません(当然そうです)が、参照によって渡すことを拒否します。彼は常に値渡しを使用します。その理由は、「参照によってオブジェクトを渡すことは、壊れたデザインの兆候である」と彼が感じているからです。

プログラムは小さなグラフィックプログラムであり、問​​題のパスのほとんどは数学的なVector(3タプル)オブジェクトです。いくつかの大きなコントローラーオブジェクトがありますが、それ以上に複雑なものはありません。

スタックだけを使用することに反対するキラーな議論を見つけるのは難しいと感じています。

ベクトルなどの小さなオブジェクトの場合、値の受け渡しは問題ありませんが、それでもコードで不要なコピーが多数発生します。大きなオブジェクトを値で渡すことは明らかに無駄であり、機能的に必要なものではない可能性があります。

プロ側では、スタックはメモリの割り当て/割り当て解除が高速で、割り当て時間が一定であると思います。

私が考えることができる唯一の主要な議論は、スタックがオーバーフローする可能性があるということですが、これが発生する可能性は低いと思いますか?参照渡しではなく、スタック/値渡しのみを使用することに反対する他の議論はありますか?

4

13 に答える 13

14

サブタイピング-ポリモーフィズムは、派生クラスをその基本クラスにスライスするため、値の受け渡しが機能しない場合です。たぶん、サブタイピングを使用すること-ポリモーフィズムは悪いデザインですか?

于 2010-09-12T15:02:05.603 に答える
7

あなたの友人の問題は彼の宗教ほど彼の考えではありません。任意の関数を指定して、値、参照、const参照、ポインター、またはスマートポインターを渡すことの長所と短所を常に考慮してください。次に決定します。

私がここで見る壊れたデザインの唯一の兆候は、あなたの友人の盲目の宗教です。

とは言うものの、テーブルにあまり影響を与えない署名がいくつかあります。constを値で取得するのはばかげているかもしれません。なぜなら、オブジェクトを変更しないと約束した場合は、独自のコピーを作成しない方がよいからです。もちろん、そのプリミティブでない限り、その場合、コンパイラはまだ参照を取得するのに十分賢い可能性があります。または、ポインタへのポインタを引数として取るのが不器用な場合もあります。これにより複雑さが増します。代わりに、ポインタへの参照を取得することでそれを回避し、同じ効果を得ることができる場合があります。

ただし、これらのガイドラインを石のように受け取らないでください。代替案の有用性を排除する正式な証明がないため、常にオプションを検討してください。

  1. 自分のニーズに合わせて引数を変更する必要があるが、クライアントに影響を与えたくない場合は、引数を値で取得します。
  2. クライアントにサービスを提供したいが、クライアントがサービスと密接に関連していない場合は、参照によって議論を行うことを検討してください。
  3. クライアントがサービスに密接に関連している場合は、引数を取らずにメンバー関数を作成することを検討してください。
  4. サービスに密接に関連しているが互いに非常に異なるクライアントのファミリーのためにサービス関数を作成したい場合は、参照引数を取ることを検討し、おそらくこの友情を必要とするクライアントの友人にすることを検討してください。
  5. クライアントをまったく変更する必要がない場合は、const-referenceを使用することを検討してください。
于 2010-09-12T15:07:23.570 に答える
6

コピーコンストラクターから始めて、参照を使用せずに実行できないあらゆる種類のことがあります。参照(またはポインター)は基本であり、彼がそれを好むかどうかにかかわらず、彼は参照を使用しています。(参照の1つの利点、またはおそらく欠点は、一般に、(const)参照を渡すためにコードを変更する必要がないことです。)そして、ほとんどの場合、参照を使用しない理由はありません。

はい、動的な割り当てを必要としない小さなオブジェクトの場合、値を渡すことは問題ありませんが、いわゆるオーバーヘッドが(a)知覚可能であり、(b)重要であるという具体的な測定なしに、「参照なし」と言って自分自身をぶち壊すのはまだ愚かです。 。「時期尚早の最適化はすべての悪の根源です」1

1 CA Hoare を含むさまざまな帰属(明らかに彼はそれを否定しているが)。

于 2010-09-12T14:53:55.307 に答える
5

質問自体には大きな誤解があると思います。

一方ではスタックまたはヒープに割り当てられたオブジェクトと、他方では値または参照またはポインターを渡すことの間に関係はありません。

スタックとヒープの割り当て

可能な場合は常にスタックを優先します。そうすれば、オブジェクトの存続期間が管理され、処理がはるかに簡単になります。

ただし、いくつかの状況では不可能な場合があります。

  • 仮想建設(工場を考えてください)
  • 共有所有権(ただし、常に回避するようにしてください)

そして、私はいくつかを見逃すかもしれませんが、この場合、例えばスマートポインターを使用することによって、スタックの存続期間管理機能を活用するためにSBRM(スコープバウンドリソース管理)を使用する必要があります。

通過:値、参照、ポインター

まず第一に、セマンティクスの違いがあります:

  • value、const reference:渡されたオブジェクトはメソッドによって変更されません
  • 参照:渡されたオブジェクトはメソッドによって変更される可能性があります
  • ポインター/定数ポインター:(動作の)参照と同じですが、nullの可能性があります

一部の言語(Haskellのような機能的な種類)は、デフォルトで参照/ポインターを提供しないことに注意してください。作成された値は不変です。外部環境に対処するためのいくつかの回避策は別として、それらはこの使用によってそれほど制限されておらず、それはどういうわけかデバッグを容易にします。

友達は、参照渡しやポインタ渡しにはまったく問題がないことを知っておく必要があります。たとえばswap、値渡しでは実装できません。

最後に、ポリモーフィズムでは値渡しのセマンティクスは許可されていません。

それでは、パフォーマンスについてお話しましょう。

ビルトインは(間接参照を避けるために)値で渡され、ユーザー定義の大きなクラスは(コピーを避けるために)参照/ポインターで渡される必要があることは通常よく受け入れられています。実際、大きいということは、一般的に、コピーコンストラクターが簡単ではないことを意味します。

ただし、小さなユーザー定義クラスに関しては未解決の質問があります。最近公開されたいくつかの記事は、場合によっては、値渡しによってコンパイラからの最適化が改善される可能性があることを示唆しています。たとえば、この場合は次のようになります。

Object foo(Object d) { d.bar(); return d; }

int main(int argc, char* argv[])
{
  Object o;
  o = foo(o);
  return 0;
}

oここで、スマートコンパイラは、コピーせずにその場で変更できることを判断できます。(関数定義が表示されている必要があると思いますが、リンク時最適化がそれを理解するかどうかはわかりません)

したがって、パフォーマンスの問題には、いつものように1つの可能性しかありませ

于 2010-09-12T16:45:08.977 に答える
4

その理由は、「参照によってオブジェクトを渡すことは、壊れたデザインの兆候である」と彼が感じているからです。

これは純粋に技術的な理由でC++では間違っていますが、初心者にとっては常に値渡しを使用することで十分な近似値です。すべてをポインターで渡すよりも(または、すべてを参照で渡すよりも)確かにはるかに優れています。一部のコードは非効率になりますが、ちょっと!これがあなたの友人を悩ませない限り、この習慣によって過度に邪魔されないでください。いつか彼が再考したいと思うかもしれないことを彼に思い出させてください。

一方、これは:

いくつかの大きなコントローラーオブジェクトがありますが、それ以上に複雑なものはありません。

問題ですあなたの友人は壊れたデザインについて話している、そしてそれからすべてのコードの使用はいくつかの3Dベクトルと大きな制御構造であるか?それは壊れたデザインです。優れたコードは、データ構造を使用してモジュール性を実現します。そうではないようです。

…そして、そのようなデータ構造を使用すると、参照渡しのないコードは実際に非常に非効率になる可能性があります。

于 2010-09-12T16:30:35.657 に答える
3

Cでポインターを使用しないことは、初心者プログラマーのサインだと思います。

友達がポインターを怖がっているようです。

C ++ポインターは実際にはC言語から継承されており、Cはコンピューターの能力がはるかに低いときに開発されたことを忘れないでください。それにもかかわらず、スピードと効率は今日まで重要であり続けます。

では、なぜポインターを使用するのでしょうか。これにより、開発者はプログラムを最適化して、他の方法よりも高速に実行したり、使用するメモリを少なくしたりできます。データのメモリ位置を参照する方が、すべてのデータをコピーするよりもはるかに効率的です。

ポインタは通常、プログラミングを始めた人にとっては理解しにくい概念です。これは、行われるすべての実験に小さな配列、おそらくいくつかの構造体が含まれるためですが、基本的には、数メガバイト(運が良ければ)での作業で構成されます。家の周りに1GBのメモリを配置します。このシーンでは、数MBは何もありません。通常、プログラムのパフォーマンスに大きな影響を与えるには少なすぎます。

それでは、少し誇張してみましょう。すべてのデータをディスクに書き込む関数に渡す必要のある2147483648要素(2GBのデータ)を持つchar配列を考えてみてください。さて、どのテクニックがより効率的/より速くなると思いますか?

  • 値渡し。プログラムがデータをディスクに書き込む前に、これらの2GBのデータをメモリ内の別の場所に再コピーする必要があります。
  • 参照渡し。これは、そのメモリ位置を参照するだけです。

4GBのRAMがない場合はどうなりますか?ポインタを使うのが怖いという理由だけで、$を使ってRAMのチップを購入しますか?

メモリ内のデータを再コピーすることは、必要がない場合は少し冗長に聞こえ、コンピュータリソースの浪費になります。

とにかく、あなたの友人に我慢してください。彼が人生のある時点で真面目でプロのプログラマーになりたいのなら、彼は最終的にはポインターを本当に理解するために時間をかけなければならないでしょう。

幸運を。

于 2010-09-12T14:52:19.840 に答える
3

まず、再帰の場合を除いて、スタックがこのWebサイトの外部にオーバーフローすることはめったにありません。

彼の推論については、彼は一般化されすぎているので間違っているかもしれないと思いますが、彼がしたことは正しいかもしれません...またはそうではありませんか?

たとえば、WindowsフォームライブラリRectangleは4つのメンバーを持つ構造体を使用し、AppleのQuartzCoreにもCGRect構造体があり、それらの構造体は常に値で渡されます。これを3つの浮動小数点変数を持つVectorと比較できると思います。

しかし、コードが見えないので、彼が何をしたかを判断するべきではないと感じていますが、彼の過度に一般化された考えにもかかわらず、彼は正しいことをしたかもしれないと感じています。

于 2010-09-12T16:01:58.983 に答える
3

ベクトルなどの小さなオブジェクトの場合、値の受け渡しは問題ありませんが、それでもコードで不要なコピーが多数発生します。大きなオブジェクトを値で渡すことは明らかに無駄であり、機能的に必要なものではない可能性があります。

あなたが思うほど明白ではありません。C ++コンパイラはコピーの省略を非常に積極的に実行するため、コピー操作のコストをかけずに値を渡すことができます。また、場合によっては、値の受け渡しがさらに高速になることもあります

パフォーマンス上の理由で問題を非難する前に、少なくともそれをバックアップするためのベンチマークを作成する必要があります。また、コンパイラは通常、パフォーマンスの違いを排除するため、作成が難しい場合があります。

したがって、本当の問題はセマンティクスの1つである必要があります。コードをどのように動作させたいですか?場合によっては、参照セマンティクスが必要な場合は、参照を渡す必要があります。特に値のセマンティクスが必要/必要な場合は、値を渡します。

値渡しを支持する1つのポイントがあります。これは、副作用が少なく、不変性がデフォルトである、より機能的なスタイルのコードを実現するのに役立ちます。これにより、多くのコードの推論が容易になり、コードの並列化も容易になる可能性があります。

しかし、実際には、どちらにも場所があります。また、参照渡しを使用しないことは、間違いなく大きな警告サインです。

過去6か月ほどの間、値渡しをデフォルトにする実験を行ってきました。参照セマンティクスが明示的に必要ない場合は、コンパイラーがコピーの省略を実行すると想定するので、効率を損なうことなく値を渡すことができます。

これまでのところ、コンパイラは私を本当にがっかりさせていません。戻っていくつかの呼び出しを参照渡しに変更しなければならない場合に遭遇することは間違いありませんが、それを知っているときにそれを行います

  • パフォーマンスが問題であり、
  • コンパイラがコピーの省略を適用できませんでした
于 2010-09-12T16:45:54.890 に答える
2

すでに述べたように、参照とポインタの大きな違いは、ポインタがnullになる可能性があることです。クラスがデータを必要とする場合、参照宣言はそれを必要とします。constを追加すると、呼び出し元が希望する場合は「読み取り専用」になります。

言及された値渡しの「欠陥」は、単に真実ではありません。すべてを値で渡すと、アプリケーションのパフォーマンスが完全に変わります。プリミティブ型(int、doubleなど)が値で渡される場合はそれほど悪くありませんが、クラスインスタンスが値で渡される場合は、一時オブジェクトが作成されます。これには、コンストラクタと、後でクラスおよびすべてでデストラクタを呼び出す必要があります。クラスのメンバー変数の。親クラスのコンストラクタ/デストラクタも呼び出す必要があるため、大規模なクラス階層を使用すると、これはさらに悪化します。

また、ベクトルが値で渡されるからといって、スタックメモリのみを使用するわけではありません。ヒープは、メソッド/関数に渡される一時ベクトルで作成されるため、各要素に使用できます。ベクトル自体も、その容量に達した場合、ヒープを介して再割り当てする必要がある場合があります。

呼び出し元の値が変更されないように値を渡す場合は、const参照を使用します。

于 2010-09-12T14:59:13.677 に答える
2

私がこれまで見てきた答えはすべて、パフォーマンスに焦点を当てています。つまり、参照渡しが値渡しよりも速い場合です。値渡しでは不可能なケースに焦点を当てると、議論でより多くの成功を収めることができます。

小さなタプルまたはベクトルは、非常に単純なタイプのデータ構造です。より複雑なデータ構造は情報を共有し、その共有を値として直接表すことはできません。参照/ポインター、または配列やインデックスなど、それらをシミュレートするものを使用する必要があります。

多くの問題は、グラフまたは有向グラフを形成するデータに要約されます。どちらの場合も、データ構造内に格納する必要のあるエッジとノードが混在しています。ここで、同じデータを複数の場所に配置する必要があるという問題があります。参照を避ける場合は、最初にデータを複製する必要があり、次にすべての変更を他の各コピーに注意深く複製する必要があります。

あなたの友人の議論は、要約すると、グラフで表すのに十分複雑な問題に取り組むことは悪い設計です...。

于 2010-09-12T16:34:43.553 に答える
2

私が考えることができる唯一の主要な議論は、スタックがオーバーフローする可能性があるということですが、これが発生する可能性は低いと思いますか?参照渡しではなく、スタック/値渡しのみを使用することに反対する他の議論はありますか?

さて、まあ、どこから始めれば...

  1. あなたが言うように、「コードで発生する不必要なコピーがたくさんあります」。これらのオブジェクトで関数を呼び出すループがあるとしましょう。オブジェクトを複製する代わりにポインターを使用すると、実行を1桁以上高速化できます。

  2. 可変サイズのデータ​​構造や配列などをスタックに渡すことはできません。それを動的に割り当て、ポインタまたは参照を先頭に渡す必要があります。あなたの友人がこれに遭遇していなければ、そうです、彼は「C++に不慣れ」です。

  3. おっしゃるように、問題のプログラムは単純で、ほとんどの場合、グラフィックス3タプルのような非常に小さなオブジェクトを使用します。これは、要素がdoubleの場合、1つあたり24バイトになります。しかし、グラフィックスでは、回転と平行移動の両方を処理する4x4配列を扱うのが一般的です。それらは1つあたり128バイトになるため、これらを処理する必要のあるプログラムが、コピーの増加により、値渡しを使用した関数呼び出しごとに5倍遅くなる場合。参照渡しでは、32ビットの実行可能ファイルで3タプルまたは4x4配列を渡すには、単一の4バイトポインターを複製するだけで済みます。

  4. ARM、PowerPC、64ビットx86、680x0などのレジスタが豊富なCPUアーキテクチャでは、32ビットx86ではなくポインタ(および、派手なシンタティックウェアを身に着けている密かにポインタである参照)は、通常、レジスタで渡されるか返されます。スタック操作に関連するメモリアクセスと比較して、非常に高速です。

  5. スタックスペースが不足する可能性は低いとおっしゃっています。そうです、クラスの割り当てのために書くかもしれない小さなプログラムではそうです。しかし、数か月前、私はmain()の下におそらく80個の関数呼び出しがある商用コードをデバッグしていました。参照渡しではなく値渡しを使用した場合、スタックは膨大なものになります。そして、これが「壊れたデザイン」だと友達に思われないように、これは実際にはGTK +を使用してLinuxに実装されたWebKitベースのブラウザーであり、すべてが非常に最先端であり、関数呼び出しの深さはプロのコードでは正常です。 。

  6. 一部の実行可能アーキテクチャは、個々のスタックフレームのサイズを制限するため、スタックスペース自体が不足していなくても、それを超えて、そのようなプラットフォームで構築されない完全に有効なC++コードになってしまう可能性があります。

私は続けることができました。

友人がグラフィックスに興味がある場合は、グラフィックスで使用される一般的なAPIのいくつかを確認する必要があります。LinuxではOpenGLとXWindows、Mac OS XではQuartz、WindowsではDirectXです。また、WebKitやGecko HTMLレンダリングエンジン、Mozillaブラウザー、GTK+やQtGUIツールキットなどの大規模なC/C++システムの内部を調べる必要があります。それらはすべて、単一の整数または参照による浮動小数点よりもはるかに大きいものを渡し、多くの場合、関数の戻り値としてではなく、参照によって結果を入力します。

深刻な現実世界のC/C ++チョップを持っている人は誰もいません。つまり、誰もデータ構造を値で渡しません。これには理由があります。それは、非効率的で問題が発生しやすいだけです。

于 2010-09-12T16:41:02.043 に答える
1

うわー、すでに13の答えがあります…私はすべてを詳細に読んでいませんでしたが、これは他のものとはかなり異なっていると思います…</ p>

彼にはポイントがあります。原則として値渡しの利点は、サブルーチンが引数を微妙に変更できないことです。非定数参照を渡すことは、すべての関数に醜い副作用があることを示し、設計が不十分であることを示します。

vector3 &との違いを彼に簡単に説明しvector3 const&、後者がのように定数によって初期化される方法を示しますが、前者はそうではありvec_function( vector3(1,2,3) );ません。パスバイ定数参照は、パスバイ値の単純な最適化です。

于 2010-09-12T18:36:42.803 に答える
0

友達に良いC++の本を買ってください。重要なオブジェクトを参照で渡すことは良い習慣であり、不必要なコンストラクタ/デストラクタの呼び出しを大幅に節約できます。これは、無料ストアへの割り当てとスタックの使用とは関係ありません。フリーストアを使用せずに、参照によってプログラムスタックに割り当てられたオブジェクトを渡すことができます(または渡す必要があります)。フリーストアを完全に無視することもできますが、それはあなたの友人がおそらく気に留めていなかった昔のFortranの時代にあなたを戻します-そうでなければ、彼はあなたのプロジェクトに古代のf77コンパイラを選ぶでしょう...?

于 2010-09-12T17:04:57.570 に答える