コンパイラの最適化に依存するコードを書くのは悪い習慣ですか?


99

私はいくつかのC ++を学習してきましたが、多くの場合、関数内で作成された関数から大きなオブジェクトを返す必要があります。参照渡し、ポインターを返し、参照型ソリューションを返すことはわかっていますが、C ++コンパイラー(およびC ++標準)が戻り値の最適化を許可していることも読んでいます。そのすべての時間とメモリを節約します。

オブジェクトが値によって明示的に返されると、構文がより明確になり、一般的にコンパイラはRVOを使用してプロセスをより効率的にするようになりました。この最適化に依存するのは悪い習慣ですか?ユーザーにとってコードがより明確で読みやすくなりますが、これは非常に重要ですが、コンパイラーがRVOの機会をつかむと仮定する必要がありますか?

これはマイクロ最適化ですか、それともコードを設計するときに留意すべきことですか?


7
あなたの編集に答えるために、それはあなたがナノ秒で稼いでいるものをベンチマークしようとしてもほとんど見えないので、マイクロ最適化です。残りの部分については、C ++で腐りすぎて、なぜ機能しないのかについての厳密な答えを提供できません。そのうちの1つは、動的割り振りが必要な場合があり、したがってnew / pointer / referencesを使用する可能性がある場合です。
ウォルフラット

4
オブジェクトがメガバイトのオーダーで非常に大きい場合でも@Walfrat?私のアレイは、私が解決しようとしている問題の性質のために巨大になる可能性があります。
マット

6
@Mattしません。このための参照/ポインタが正確に存在します。コンパイラーの最適化は、プログラムを構築する際にプログラマーが考慮すべき以上のものであると想定されています。
ニール

5
@Matt C /カーネルで10以上の経験を持つ開発者を必要とするような非常に具体的なことをしている場合を除き、ハードウェアとのやり取りは必要ないはずです。あなたが何か特別なものに属していると思うなら、あなたの投稿を編集して、あなたがアプリケーションがすることになっていることの正確な説明を追加してください(リアルタイム?重い数学計算?...)
Walfrat

37
では、特定の場合 C ++の(N)RVOの、はい、この最適化に頼ることは完全に有効です。これは、最新のコンパイラがすでにそれを行っていた状況で、C ++ 17標準がそれが起こることを明確に命じているためです。
カレス

回答:


130

最小限の驚きの原則を採用ます。

このコードを使用するのはあなただけで、これまでに3年間で同じことをしても、あなたが何をするかに驚かないことは確かですか?

その後、先に行きます。

その他の場合はすべて、標準的な方法を使用します。そうしないと、あなたとあなたの同僚はバグを見つけるのが難しくなります。

たとえば、私の同僚は私のコードがエラーを引き起こしていると不平を言っていました。結局、彼はコンパイラー設定で短絡ブール評価をオフにしていたことがわかりました。私はほとんど彼を平手打ちしました。


88
@Neilが私のポイントです。誰もが短絡評価に依存しています。そして、あなたはそれについて再考する必要はないはずです、オンにする必要があります。これは事実上の標準です。はい、変更できますが、変更しないでください。
ピーターB

49
「言語の動作を変更し、あなたの汚い腐ったコードが壊れました!ああ!」ワオ。叩くのが適切で、同僚をZenのトレーニングに送ります。たくさんあります。

109
@PieterB C C ++の言語仕様が短絡評価を保証している確信しています。したがって、それは単なる事実上の標準ではなく標準です。それがなければ、あなたはもうC / C ++さえ使用していませんが、それが疑わしいようなものです:P
marcelm

47
参考のために、ここでの標準的な方法は値で返すことです。
DeadMG

28
@ dan04はい、それはDelphiにありました。みんな、私が言ったポイントについての例に巻き込まれないでください。誰もしない驚くようなことをしないでください。
ピーターB

81

この特定のケースでは、必ず値で返ってください。

  • RVOとNRVOは、C ++ 03モードであっても、まともなコンパイラーによって実際に行われるべきである、よく知られた強力な最適化です。

  • 移動セマンティクスにより、(N)RVOが実行されなかった場合にオブジェクトが関数から移動することが保証されます。あなたのオブジェクトは、内部で動的データを使用している場合、それは(のようなだけで便利ですstd::vectorし)、それがあれば、それは本当にケースであるべきことを大-スタックをオーバーフローが大きな自動オブジェクトと危険です。

  • C ++ 17には、強制 RVOを。したがって、心配する必要はありません。消えることはなく、コンパイラが最新の状態になって初めて完全に確立されます。

そして最後に、追加の動的割り当てを強制的にポインターを返すか、結果タイプをデフォルトで構築可能にして、出力パラメーターとして渡すことができるようにします持ってる。

意味のあるコードを書くだけで、意味のあるコードを正しく最適化してくれたコンパイラの作者に感謝します。


9
楽しみのために、1990年代のBorland Turbo C ++ 3.0がどのようにRVOを処理するかを見てください。ネタバレ:基本的には問題なく動作します。
nwp

9
ここで重要なのは、ランダムなコンパイラ固有の最適化や「ドキュメント化されていない機能」ではなく、C ++標準のいくつかのバージョンでは技術的にオプションであるものの、業界によって大きく推進され、ほとんどすべての主要なコンパイラがそれを行っていることですとても長い間。

7
この最適化は、好むほど堅牢ではありません。はい、それは最も明白なケースではかなり信頼できますが、gccのbugzillaを例にとると、ほとんど見逃せない多くのケースがあります。
マークグリッセ

62

オブジェクトが値によって明示的に返されると、構文がより明確になり、一般的にコンパイラはRVOを使用してプロセスをより効率的にするようになりました。この最適化に依存するのは悪い習慣ですか?ユーザーにとってコードがより明確で読みやすくなりますが、これは非常に重要ですが、コンパイラーがRVOの機会をつかむと仮定する必要がありますか?

これは、小さな、人身売買の少ないブログで読んでいる、あまり知られていないかわいらしい、マイクロ最適化ではありません。

C ++ 11以降、RVOはこのコードのコードを記述する標準的な方法です。一般的で、予想され、教えられ、講演で言及され、ブログで言及され、標準で言及され、実装されなければコンパイラーのバグとして報告されます。C ++ 17では、言語はさらに一歩進んで、特定のシナリオでコピーの省略を義務付けています。

この最適化に絶対に頼るべきです。

その上、値による戻りは、参照によって返されるコードよりも読み取りおよび管理が非常に簡単なコードになります。値セマンティクスは強力なものであり、それ自体が最適化の機会を増やす可能性があります。


3
おかげで、これは非常に理にかなっており、上記の「最低の驚きの原則」と一致しています。これにより、コードが非常に明確で理解しやすくなり、ポインターシェナンガンを台無しにするのが難しくなります。
マット

3
@Mattこの答えを支持した理由の一部は、「値のセマンティクス」に言及していることです。C ++(および一般的なプログラミング)でより多くの経験を積むにつれて、特定のオブジェクトが可変であり、その変更をその同じオブジェクトを使用する他のコードから見えるようにする必要があるため、値セマンティクスを使用できない場合があります( 「共有可変性」の例)。これらの状況が発生すると、影響を受けるオブジェクトを(スマート)ポインターを介して共有する必要があります。
-rwong

16

記述したコードの正確性は、最適化に依存することはありません。仕様で使用しているC ++「仮想マシン」で実行すると、正しい結果が出力されます。

ただし、あなたが話しているのは、より効率的な質問です。RVO最適化コンパイラで最適化すると、コードの実行が向上します。他の回答で指摘されたすべての理由から、それは問題ありません。

ただし、この最適化が必要な場合(コピーコンストラクターによって実際にコードが失敗する場合など)、コンパイラーの気まぐれになります。

私自身の実践におけるこれの最良の例は、テールコールの最適化だと思います。

   int sillyAdd(int a, int b)
   {
      if (b == 0)
          return a;
      return sillyAdd(a + 1, b - 1);
   }

馬鹿げた例ですが、関数の最後に関数が再帰的に呼び出される末尾呼び出しを示しています。C ++仮想マシンは、このコードが適切に動作することを示しますが、そもそもなぜこのような追加ルーチンを作成するのが面倒なの、少し混乱するかもしれません。ただし、C ++の実際の実装では、スタックがあり、スペースが限られています。慎重に行われた場合、この関数はb + 1追加時に少なくともスタックフレームをスタックにプッシュする必要があります。計算したい場合sillyAdd(5, 7)、これは大したことではありません。を計算したい場合sillyAdd(0, 1000000000)、StackOverflowを引き起こすという本当の問題が発生する可能性があります(良い種類ではない)。

ただし、その最後の戻り行に到達すると、現在のスタックフレーム内のすべての処理が完了したことがわかります。実際に保持する必要はありません。テールコールの最適化により、既存のスタックフレームを次の機能に「再利用」できます。このようにして、必要なスタックフレームは1つだけでなく1つですb+1。(これらの愚かな加算と減算をすべて行う必要がありますが、それらはより多くのスペースを必要としません。)実際、最適化によりコードは次のようになります。

   int sillyAdd(int a, int b)
   {
      begin:
      if (b == 0)
          return a;
      // return sillyAdd(a + 1, b - 1);
      a = a + 1;
      b = b - 1;
      goto begin;  
   }

一部の言語では、テールコールの最適化が仕様で明示的に要求されています。C ++はそれらの1つではありません。ケースバイケースで進めない限り、C ++コンパイラに頼ってこのテールコール最適化の機会を認識することはできません。私のバージョンのVisual Studioでは、リリースバージョンはテールコールの最適化を行いますが、デバッグバージョンは(設計上)行いません。

したがって、私が計算できることに依存するのは悪いでしょうsillyAdd(0, 1000000000)


2
これは興味深いコーナーケースですが、最初の段落のルールに一般化できるとは思いません。コンパイラのサイズ削減最適化を使用する場合にのみロードする小さなデバイス用のプログラムがあるとします-そうするのは間違っていますか?特に、その書き換えが問題を解決するためにオプティマイザーと同じことを行う場合、私の唯一の有効な選択はアセンブラーでそれを書き換えることであると言うのはかなり屈なようです。
-sdenham

5
@sdenham引数には少し余地があると思います。「C ++」用ではなく「WindRiver C ++コンパイラバージョン3.4.1」用に作成している場合は、そこにロジックが表示されます。ただし、一般的なルールとして、仕様に従って適切に機能しないものを書いている場合、非常に異なる種類のシナリオになります。Boostライブラリにはそのようなコードがありますが、それらは常に#ifdefブロックに入れられ、標準に準拠した回避策が用意されています。
コートアンモン

4
それはそれが言うところのコードの2番目のブロックのタイプミスb = b + 1ですか?
-stib

2
「C ++仮想マシン」とは、標準ドキュメントで使用されている用語ではないため、その意味を説明したい場合があります。私あなたがC ++の実行モデルについて話していると思いますが、完全に確実ではありません-あなたの用語はまったく違うものに関連する「バイトコード仮想マシン」に一見似ています。
トビーSpeight

1
@supercat Scalaには明示的な末尾再帰構文もあります。C ++はそれ自身の獣ですが、テール再帰は非機能言語では単言語であり、機能言語では必須であり、明示的なテール再帰構文を使用するのが妥当な少数の言語を残していると思います。文字通り、末尾再帰をループと明示的な突然変異に変換することは、多くの言語にとってより良い選択肢です。
-prosfilaes

8

実際には、 C ++プログラムはコンパイラの最適化を期待しています。

特に、標準コンテナ実装の標準ヘッダーを調べてください。ではGCC、あなたは前処理フォーム(求めることができるg++ -C -E)と(GIMPLE内部表現g++ -fdump-tree-gimpleを持つか、GIMPLE SSA -fdump-tree-ssaコンテナを使用して、ほとんどのソースファイルの)(技術的には翻訳単位)。(でg++ -O2)行われた最適化の量に驚くでしょう。そのため、コンテナの実装者は最適化に依存します(そしてほとんどの場合、C ++標準ライブラリの実装者は最適化が行われることを知っており、それらを念頭に置いてコンテナ実装を記述します。時にはコンパイラの最適化パスも記述します標準C ++ライブラリで必要な機能を処理します)。

実際には、C ++とその標準コンテナーを十分に効率的にするのは、コンパイラーの最適化です。だからあなたはそれらに頼ることができます。

そして、あなたの質問で言及されたRVOの場合も同様です。

C ++標準は、可能な最適化とうまく機能するように(特に、新しい機能を提案しながら十分に最適化を実験することによって)共同設計されました。

たとえば、以下のプログラムを検討してください。

#include <algorithm>
#include <vector>

extern "C" bool all_positive(const std::vector<int>& v) {
  return std::all_of(v.begin(), v.end(), [](int x){return x >0;});
}

でコンパイルしg++ -O3 -fverbose-asm -Sます。生成された関数はCALL機械語命令を実行しないことがわかります。そのため、ほとんどのC ++ステップ(ラムダクロージャの構築、その繰り返しアプリケーション、beginおよびendイテレータの取得など)が最適化されています。マシンコードにはループのみが含まれています(ループはソースコードには明示的に表示されません)。このような最適化がなければ、C ++ 11は成功しません。

補遺

(追加12月31日ST 2017)

CppCon 2017:Matt Godboltをご覧くださいコンパイラの蓋のボルトを外す」トーク。


4

コンパイラを使用するときはいつでも、マシンまたはバイトコードを生成するという理解です。言語の仕様に従ってソースコードを実装することを除き、生成されたコードがどのようなものであるかについては何も保証しません。この保証は、使用される最適化のレベルに関係なく同じであるため、一般に、1つの出力を他の出力よりも「正しい」と見なす理由はありません。

さらに、RVOのように言語で指定されている場合、特にソースコードを単純化する場合は、使用を避けるために邪魔にならないように無意味に思えます。

コンパイラーが効率的な出力を生成するように多くの努力が注がれていますが、明らかに、それらの機能を使用することが意図されています。

最適化されていないコード(デバッグなど)を使用する理由があるかもしれませんが、この質問に記載されているケースは1つではないようです(最適化された場合にのみコードが失敗し、それがあなたがそれを実行しているデバイス、どこかにバグがあり、コンパイラにある可能性は低いです。)


3

他の人はC ++とRVOの特定の角度をうまくカバーしていると思います。一般的な答えは次のとおりです。

正確性に関しては、コンパイラーの最適化や一般的なコンパイラー固有の動作に依存しないでください。幸いなことに、あなたはこれをやっていないようです。

パフォーマンスに関しては、コンパイラ固有の動作全般、特にコンパイラの最適化に依存する必要あります。標準に準拠したコンパイラは、コンパイルされたコードが言語仕様に従って動作する限り、コードを自由にコンパイルできます。また、各操作の速度を指定する主流言語の仕様についても知りません。


1

コンパイラーの最適化はパフォーマンスにのみ影響し、結果には影響しません。非機能要件を満たすためにコンパイラーの最適化に依存することは合理的であるだけでなく、あるコンパイラーが別のコンパイラーよりも優先される理由であることがよくあります。

特定の操作の実行方法を決定するフラグ(たとえば、インデックス条件またはオーバーフロー条件)は、コンパイラの最適化で頻繁にまとめられますが、そうすべきではありません。それらは計算の結果に明示的に影響します。

コンパイラーの最適化によって異なる結果が生じる場合、それはバグ、つまりコンパイラーのバグです。コンパイラのバグに依存することは、長期的には間違いです。それが修正されるとどうなりますか?

計算の動作方法を変更するコンパイラフラグの使用については、十分に文書化する必要がありますが、必要に応じて使用します。


残念なことに、多くのコンパイラのドキュメントでは、さまざまなモードで保証されるものと保証されないものを指定するのは不十分です。さらに、「最新の」コンパイラの作成者は、プログラマが必要とする必要のない保証の組み合わせを忘れているようです。x*y>zオーバーフローの場合に任意で0または1が得られた場合にプログラムが正常に動作する場合、他の副作用がない場合、プログラマーはすべてのコストでオーバーフローを防止するか、特定の方法で式を評価するようコンパイラーに強制する必要があります言うまでもなく、最適化を損なう
...-supercat

...コンパイラは可能性があります、そのレジャーであるかのように振る舞いx*y、いくつかの任意の長いタイプ(一部のオーバーフロー例の動作を変更します巻き上げと強度の減少のため、可能な形態)にそのオペランドを推進しています。ただし、多くのコンパイラでは、プログラマがすべてのコストでオーバーフローを防止するか、オーバーフローが発生した場合にコンパイラがすべての中間値を切り捨てるようにする必要があります。
-supercat

1

番号。

それは私がいつもやっていることです。メモリ内の任意の16ビットブロックにアクセスする必要がある場合、これを行います

void *ptr = get_pointer();
uint16_t u16;
memcpy(&u16, ptr, sizeof(u16)); // ntohs omitted for simplicity

...そして、そのコードを最適化するためにできる限りのことを行うコンパイラーに依存しています。このコードは、ARM、i386、AMD64、および事実上すべての単一アーキテクチャで動作します。理論的には、最適化されていないコンパイラは実際にを呼び出すことができmemcpy、結果としてまったくパフォーマンスが低下しますが、コンパイラの最適化を使用しているので、それは問題ではありません。

代替案を検討してください:

void *ptr = get_pointer();
uint16_t *u16ptr = ptr;
uint16_t u16;
u16 = *u16ptr;  // ntohs omitted for simplicity

この代替コードget_pointer()は、位置合わせされていないポインターを返す場合、適切な位置合わせが必要なマシンでは機能しません。また、代替案にエイリアスの問題があるかもしれません。

memcpyトリックを使用する場合の-O2と-O0の違いは大きく、3.2 GbpsのIPチェックサムパフォーマンスと67 GbpsのIPチェックサムパフォーマンスです。桁違いの違い!

コンパイラを支援する必要がある場合があります。したがって、たとえば、ループを展開するためにコンパイラに依存する代わりに、自分でそれを行うことができます。有名なDuffのデバイスを実装するか、よりクリーンな方法で。

コンパイラーの最適化に依存することの欠点は、コードをデバッグするためにgdbを実行すると、多くの最適化が行われていることがわかる場合があることです。そのため、-O0を使用して再コンパイルする必要がある場合があります。これは、デバッグ時にパフォーマンスが完全に低下することを意味します。コンパイラーを最適化する利点を考えると、これは取るに値する欠点だと思います。

あなたが何をするにしても、あなたのやり方が実際に未定義の振る舞いではないことを確認してください。16ビット整数としてメモリのランダムブロックにアクセスすることは、エイリアシングとアライメントの問題のため、未定義の動作です。


0

アセンブリ以外で書かれた効率的なコードの試みはすべて、コンパイラの最適化に非常に大きく依存しています。効率的なレジスタ割り当てのような最も基本的なものから始まり、余計なスタックの流出を回避し、少なくとも優れたものではないにしても、合理的な命令選択を行います。さもなければ、80年代に戻って、あちこちにregisterヒントを配置し、関数内の変数の最小数を使用して、古風なCコンパイラを支援するか、さらにgoto便利な分岐最適化が必要になったときでした。

コードを最適化するオプティマイザーの能力に頼ることができないと感じた場合、アセンブリでパフォーマンスが重要な実行パスをコーディングしていることになります。

最適化をどの程度確実に行うことができるかは、あなたが持っているコンパイラの機能をプロファイリングして調べることで整理され、コンパイラがどこにあるのかわからないホットスポットがある場合は分解することもできます明らかな最適化に失敗しました。

RVOは長年にわたって存在してきたものであり、少なくとも非常に複雑なケースを除いて、コンパイラーが長年にわたって確実に適切に適用してきたものです。存在しない問題を回避する価値はありません。

恐れることなく、オプティマイザーに依存する側のエラー

それどころか、少なすぎるよりもコンパイラーの最適化に頼りすぎているということは間違いではなく、この提案は、顧客の間で効率、保守性、および知覚される品質が非常に重要なパフォーマンスクリティカルな分野で働いている人から来ていますすべて1つの巨大なぼかし。オプティマイザーに自信を持って頼り、あまり頼りにせずに頼りすぎて、生涯ずっと迷信的な恐怖からコーディングしているあいまいなエッジケースを見つけたいと思います。少なくともプロファイラーに手を伸ばして、物事が必要な速度で実行されない場合は適切に調査し、途中で迷信ではなく貴重な知識を得る必要があります。

オプティマイザーに頼ってうまくやっています。がんばり続ける。オプティマイザーの欠点に対する誤った恐怖をプロファイリングする前に、ループで呼び出されるすべての関数を明示的にインライン化するように要求し始めるような男にならないでください。

プロファイリング

プロファイリングは実際には回り道ですが、あなたの質問に対する究極の答えです。効率の良いコードを書くことに熱心な初心者がしばしば苦労するのは、最適化するものではなく、人間にとって直観的ではあるが計算上間違っている非効率性に関するあらゆる種類の誤った方向性を開発するため、最適化しないことです。プロファイラーの使用経験を積むことで、自信を持って使用できるコンパイラーの最適化機能だけでなく、ハードウェアの機能(および制限)も適切に評価できるようになります。最適化する価値のないものを学ぶことで、プロファイリングすることは、何を学ぶことよりもさらに価値が高いと言えます。


-1

ソフトウェアは、非常に異なるプラットフォームのC ++で、さまざまな目的のために作成できます。

それは完全にソフトウェアの目的に依存します。メンテナンス、拡張、パッチ適用、リファクタリングなどが簡単な場合。または、パフォーマンス、コスト、特定のハードウェアとの互換性、開発にかかる時間など、より重要なものです。


-2

これに対する退屈な答えは「それは依存する」だと思います。

それはコンパイラの最適化に依存しているコード書くための悪い習慣でオフにされる可能性が高いとして、脆弱性が文書化されていませんが、どこで問題のコードは、ユニットテストされていません、それは壊れなかった場合、あなたはそれを知っていると思いますように?恐らく。

コンパイラーの最適化に依存するコードを書くのは悪い習慣ですか?コンパイラーの最適化はオフされる可能性が低く文書化されユニットテストされていますか?そうでないかもしれない。


-6

あなたが私たちに言っていないことがもっとない限り、これは悪い習慣ですが、あなたが提案する理由のためではありません。

以前使用した他の言語とは異なり、C ++でオブジェクトの値を返すと、オブジェクトのコピーが生成されます。その後、オブジェクトを変更すると、別のオブジェクトが変更されます。それは私が持っている場合は、あるObj a; a.x=1;Obj b = a;その後、私は、b.x += 2; b.f();それから、a.xまだ3は、1に等しいではありません。

そのため、参照またはポインタとしてではなく値としてオブジェクトを使用しても同じ機能は提供されず、ソフトウェアのバグが発生する可能性があります。

おそらくあなたはこれを知っていて、それはあなたの特定のユースケースに悪影響を与えません。ただし、質問の文言に基づいて、この区別に気付いていないようです。「関数にオブジェクトを作成する」などの文言。

「関数でオブジェクトを作成する」new Obj;は「値でオブジェクトを返す」のように聞こえますObj a; return a;

Obj a;そしてObj* a = new Obj;、非常に異なるものです。前者は適切に使用および理解されない場合はメモリ破損を引き起こす可能性があり、後者は適切に使用および理解されない場合はメモリリークを引き起こす可能性があります。


8
戻り値の最適化(RVO)は明確に定義されたセマンティクスであり、コンパイラは返されるオブジェクトをスタックフレームの1レベル上に構築し、特に不要なオブジェクトのコピーを回避します。これは明確に定義された動作であり、C ++ 17で義務付けられるずっと前にサポートされています。10〜15年前でも、すべての主要なコンパイラがこの機能をサポートしており、一貫して機能していました。

@Snowman物理的な低レベルのメモリ管理については話しておらず、メモリの膨張や速度についても話しませんでした。回答で具体的に示したように、論理データについて話している。論理的には、コンパイラの実装方法や舞台裏で使用されるアセンブリに関係なく、オブジェクトの値を提供することでオブジェクトのコピーが作成されます。舞台裏の低レベルのものは別のものであり、言語の論理構造と動作は別のものです。それらは関連していますが、同じものではありません-両方とも理解されるべきです。
アーロン

6
あなたの答えは、「C ++でオブジェクトの値を返すとオブジェクトのコピーが生成される」というもので、RVOのコンテキストでは完全に偽です。オブジェクトは呼び出し元で直接構築れ、コピーは作成されません。これをテストするには、コピーコンストラクターを削除し、RVOの要件であるreturnステートメント構築されたオブジェクトを返します。さらに、次にキーワードnewとポインターについて話をしますが、これはRVOの目的ではありません。あなたは質問を理解していないか、RVO、あるいはその両方を理解していないと思います。

-7

Pieter Bは、最小限の驚きを推奨するという点で完全に正しいです。

特定の質問に答えるために、C ++でこれが意味する可能性が最も高いのはstd::unique_ptr、構築されたオブジェクトにa を返すことです。

理由は、これが何が起こっているかに関してC ++開発者にとってより明確からです。

あなたのアプローチはおそらく機能するでしょうが、実際にはそうではないのに、オブジェクトが小さな値型であることを効果的に伝えています。さらに、インターフェイスの抽象化の可能性を捨てています。これは現在の目的には問題ないかもしれませんが、行列を扱う場合に非常に便利です。

他の言語から来た場合、最初はすべてのシギルが混乱する可能性があることを感謝します。ただし、それらを使用しないことで、コードが明確になると想定しないように注意してください。実際には、逆のことが当てはまる可能性があります。


ローマにいるときは、ローマ人と同じようにしてください。

14
これは、動的割り当てを実行しないタイプには適していません。OPが彼のユースケースで自然なことだと感じるのは、値で戻ることであり、彼のオブジェクトが呼び出し側で自動ストレージ期間を持っていることを示しています。単純で大きすぎないオブジェクトの場合、単純な戻り値のコピーの実装でさえ、動的割り当てよりも桁違いに高速です。(一方で、関数がコンテナーを返す場合、unique_pointerを返すことは、単純な値によるコンパイラーの戻り値と比較して有利な場合もあります。)
ピーターA.シュナイダー

9
@Mattこれがベストプラクティスではないことに気付いていない場合。不必要にメモリ割り当てを行い、ユーザーにポインタのセマンティクスを強制するのは悪いことです。
-nwp

5
まず、スマートポインターを使用する場合std::make_uniqueは、std::unique_ptr直接ではなくを返す必要があります。第二に、RVOは、難解なベンダー固有の最適化ではありません。標準に組み込まれています。そうでなかったときでさえ、広くサポートされ、予想される動作でした。そもそもstd::unique_ptrポインターが必要ないときにaを返すことは意味がありません。

4
@スノーマン:「そうではなかったとき」はありません。最近になって必須になりましたが、すべてのC ++標準が[N] RVOを認識し、それを有効にするための調整を行いました(たとえば、たとえコンパイラが、目に見える副作用があります)。
ジェリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.