web-dev-qa-db-ja.com

autoはC ++コードを理解しにくくしますか?

Herb Sutterによるカンファレンスを見たところ、彼はすべてのC++プログラマにautoの使用を勧めています。

varが広範囲に使用されていて、コードが非常に理解しづらかったC#コードを少し前に読む必要がありました。varが使用されるたびに、右側の戻り値の型を確認する必要がありました。しばらくすると、変数の型を忘れてしまったため、ときどき1回以上です。

コンパイラーがタイプを認識していることはわかっているので、それを記述する必要はありませんが、コンパイラーではなくプログラマー向けにコードを記述する必要があることは広く受け入れられています。

私はそれが書くのがより簡単であることも知っています:

_auto x = GetX();
_

より:

_someWeirdTemplate<someOtherVeryLongNameType, ...>::someOtherLongType x = GetX();
_

ただし、これは1回だけ書き込まれ、xの型を理解するためにGetX()戻り型が何度もチェックされます。

これは私に不思議に思いました—autoはC++コードを理解しにくくしていますか?

127
Mircea Ispas

短い回答:より完全に言うと、autoに対する私の現在の意見は、明示的に変換が必要でない限り、デフォルトでautoを使用するべきだというものです。 (少し正確に言うと、「型に明示的にコミットしたい場合を除いて、ほとんどの場合、変換が必要だからです。」)

より長い答えと根拠:

autoではなく)明示的な型を書き込むのは、本当に型に明示的にコミットする場合のみです。これは、ほとんどの場合、その型への変換を明示的に取得することを意味します。私の頭の上の2つの主なケースを思い出します。

  • (共通)_initializer_list_が_auto x = { 1 };_を演繹する_initializer_list_サプライズ。 _initializer_list_が不要な場合は、タイプを言います。つまり、コンバージョンを明示的に要求します。
  • (まれに)__auto x = matrix1 * matrix 2 + matrix3;_などの式テンプレートのケースは、プログラマーから見えるように意図されていないヘルパーまたはプロキシタイプをキャプチャします。多くの場合、その型をキャプチャしても問題ありませんが、本当に折りたたんで計算を実行したい場合は、型を言います。つまり、変換を明示的に要求します。

それ以外の場合は、デフォルトでautoを日常的に使用します。これは、autoを使用すると、落とし穴が回避され、コードがより正確になり、保守性と堅牢性が高まり、効率が上がるためです。大まかに重要性の低いものから順に、「明確さと正確さを第一に書く」という精神で:

  • 正しさ:autoを使用すると、正しいタイプが確実に得られます。ことわざにあるように、自分を繰り返す(タイプを冗長に言う)と、嘘をつくことができます(誤解してください)。次に、通常の例を示します。void f( const vector<int>& v ) { for( /*…*-この時点で、イテレータのタイプを明示的に記述した場合、_const_iterator_(そうでしたか?)を忘れずに記述したいのに対し、autoは正しく機能します。
  • 保守性と堅牢性:autoを使用すると、式が変更されてもautoが正しい値に解決し続けるため、変更に直面してもコードがより堅牢になりますタイプ。代わりに明示的な型にコミットする場合、式の型を変更すると、新しい型が古い型に変換されるときにサイレント変換が挿入されます。または、新しい型が古い型のように機能するが古い型に変換されない場合は、不要なビルドブレークが発生します。タイプ(たとえば、mapを_unordered_map_に変更すると、順序に依存しない場合は常に問題ありません。autoをイテレータに使用すると、_map<>::iterator_から_unordered_map<>::iterator_、ただし、どこでも_map<>::iterator_を明示的に使用すると、インターンが通り過ぎて退屈な作業をやめることができない限り、機械的なコード修正リップルで貴重な時間を浪費することになります)。
  • パフォーマンス:autoは暗黙的な変換が行われないことを保証するため、デフォルトではより優れたパフォーマンスが保証されます。代わりにタイプを言って、それが変換を必要とする場合、予期したかどうかにかかわらず、黙って変換を取得することがよくあります。
  • 使いやすさ:autoを使用することは、ラムダやテンプレートヘルパーなど、スペルが難しく、発話できないタイプに対して、反復的なdecltypeに頼るのではなく、唯一の優れたオプションです式または_std::function_などの非効率的な間接参照。
  • コンビニエンス:そして、はい、autoは入力が少なくなります。完全を期すために最後に触れますが、これは好きになる一般的な理由ですが、それを使用する最大の理由ではありません。

したがって:デフォルトでautoと言うことをお勧めします。シンプルさ、パフォーマンス、明快さの点で非常に優れているので、そうしなくても自分(およびコードの将来のメンテナ)を傷つけるだけです。明示的な型にコミットするのは、本当にそれを意味する場合に限ってください。ほとんどの場合、明示的な変換が必要であることを意味します。

はい、これについて(今) GotW があります。

101
Herb Sutter

それはケースバイケースの状況です。

コードを理解することが難しくなる場合もあれば、そうでない場合もあります。例えば:

void foo(const std::map<int, std::string>& x)
{
   for ( auto it = x.begin() ; it != x.end() ; it++ )
   { 
       //....
   }
}

確かに理解しやすいおよび実際のイテレータ宣言よりも明らかに簡単です。

私はしばらくC++を使用していますが、const_iteratorを忘れて最初にiterator... :)

このような場合に使用しますが、実際に型を難読化する場所(状況など)ではありませんが、これは純粋に主観的なものです。

112
Luchian Grigore

別の方法で見てください。あなたは書きますか:

std::cout << (foo() + bar()) << "\n";

または:

// it is important to know the types of these values
int f = foo();
size_t b = bar();
size_t total = f + b;

std::cout << total << "\n";

場合によっては、タイプを明示的に綴るのに役立ちません。

型について言及する必要があるかどうかの決定は、中間変数を定義してコードを複数のステートメントに分割するかどうかという決定とは異なります。 C++ 03では2つがリンクされていましたが、autoはそれらを分離する方法と考えることができます。

型を明示的にすると便利な場合があります。

// seems legit    
if (foo() < bar()) { ... }

vs.

// ah, there's something tricky going on here, a mixed comparison
if ((unsigned int)foo() < bar()) { ... }

変数を宣言する場合、autoを使用すると、多くの式の場合と同じように、型が読み上げられなくなります。読みやすさを向上させるタイミングと、それが妨げられるタイミングを自分で判断する必要があります。

符号付きの型と符号なしの型を混在させることは、最初は間違いであると主張できます(実際、符号なしの型をまったく使用してはならないと主張する人もいます)。 reasonこれは間違いですが、動作が異なるため、オペランドのタイプが非常に重要になります。値の型を知る必要があることが悪いことである場合、それらを知る必要がないこともおそらく悪いことではありません。したがって、コードが他の理由でまだ混乱していない場合、autoは大丈夫ですか? ;-)

特に一般的なコードを書くとき、変数の実際の型すべきではないが重要である場合があります。重要なのは、それが必要なインターフェースを満たすことです。したがって、autoは、型を無視する抽象化のレベルを提供します(もちろん、コンパイラーはそうではありません)。適切な抽象化レベルで作業すると、可読性が大幅に向上します。「間違った」レベルで作業すると、コードの読み取りが難しくなります。

94
Steve Jessop

IMO、あなたはこれをかなり逆に見ています。

判読不能またはさらに可読性の低いコードにつながるautoの問題ではありません。戻り値に明示的な型があること(それが期待できること)の問題は、特定の関数によって返される型が(明らかに)明確でないという事実を補うことです。

少なくとも私の意見では、戻り値の型がすぐに分からない関数がある場合、それはそこに問題があります。関数の機能はその名前から明らかであり、戻り値の型はその機能から明らかである必要があります。そうでない場合は、それが問題の本当の原因です。

ここに問題がある場合は、autoではありません。それはコードの残りの部分であり、明示的なタイプは、コアの問題を見たり修正したりするのを防ぐのに十分なバンドエイドである可能性がかなり高いです。その実際の問題を修正すると、autoを使用したコードの可読性は一般的に問題ありません。

公平に言えば、追加する必要があると思います。そのようなことがあなたの望むほど明白ではなかったいくつかのケースに対処し、問題を修正することもかなり受け入れられませんでした。ほんの一例として、2年前に別の会社と合併していた会社のコンサルティングを行いました。最終的には、実際にマージするよりも「一緒に押し込まれた」コードベースになりました。構成要素のプログラムは、似たような目的で異なる(ただし、非常に似た)ライブラリを使用し始めており、よりきれいにマージするように取り組んでいましたが、実際にはそうでした。多くの場合、特定の関数によって返されるタイプを推測する唯一の方法は、その関数がどこから発生したかを知ることでした。

そのような場合でも、かなりの数を明確にするのに役立ちます。その場合、すべてのコードはグローバル名前空間で始まりました。一部の名前空間にかなりの量を移動するだけで、名前の衝突がなくなり、型の追跡もかなり簡単になりました。

27
Jerry Coffin

私が一般的な用途でautoを嫌う理由はいくつかあります。

  1. コードを変更せずにリファクタリングできます。はい、これはautoを使用する利点としてよくリストされるものの1つです。関数の戻り値の型を変更するだけで、それを呼び出すすべてのコードがautoを使用する場合、追加の作業は必要ありません。コンパイルを押すと、ビルドが作成され、警告が0、エラーが0になり、関数が使用されている80の場所を調べて変更する必要がないため、コードをチェックインするだけで済みます。

しかし、待ってください、それは本当に良い考えですか?型がそれらのユースケースの6ダースで重要であり、今やそのコードが実際に異なる動作をする場合はどうなりますか?また、入力値だけでなく、関数を呼び出す他のクラスのプライベート実装の動作自体を変更することにより、カプセル化を暗黙的に解除することもできます。

1a。私は「自己文書化コード」の概念を信じています。自己文書化コードの背後にある理由は、コメントが古くなり、コードが何をしているかを反映しなくなる傾向があるのに対し、コード自体は-明確な方法で記述されている場合-自明であり、常に最新の状態を保つその意図に基づいており、古いコメントと混同しないでください。ただし、コード自体を変更せずにタイプを変更できる場合、コード/変数自体が古くなる可能性があります。例えば:

自動bThreadOK = CheckThreadHealth();

問題を除いて、ある時点でCheckThreadHealth()がリファクタリングされ、boolではなくエラーステータスを示す列挙値が返されました。しかし、その変更を行った人はこの特定のコード行の検査に失敗し、警告やエラーなしでコンパイルされたコンパイラは役に立ちませんでした。

  1. 実際のタイプが何であるかは決してわかりません。これは多くの場合、自動車の主要な「利点」としてリストされます。 「だれが気にするか?コンパイルする!」

たぶんそれは一種の作品です。ループの反復ごとに500バイトの構造体のコピーを作成しているので、その上で単一の値を検査できるので、コードは完全に機能します。そのため、単体テストでさえ、その単純で無害に見えるautoの後ろに悪いコードが隠れていることに気付く助けにはなりません。ファイルをスキャンする他のほとんどの人も、一見しただけでは気付かないでしょう。

これは、タイプがわからない場合でも悪化する可能性がありますが、それが何であるかについて間違った仮定をする変数名を選択すると、実際には1aと同じ結果が得られますが、最初からではなく、リファクタリング後。

  1. 最初の書き込み時にコードを入力することは、プログラミングの最も時間のかかる部分ではありません。はい、autoを使用すると、最初に一部のコードをより速く作成できます。免責事項として、私は100 WPMを超えるタイプを入力しているので、他の人ほど気にしないかもしれません。しかし、私がしなければならなかったすべてが一日中新しいコードを書くことであったら、私は幸せなキャンパーになるでしょう。プログラミングで最も時間がかかる部分は、コード内の再現が難しいEdgeケースのバグを診断することです。これは、autoの種類の過度の使用などが原因となる可能性が高い、明白ではない問題から生じることが多いです(参照とコピー、符号付きと符号なし、浮動小数点と整数、ブールとポインタなど)。

Autoが主に標準ライブラリテンプレートタイプのひどい構文の回避策として導入されたことは明らかです。人々がすでに知っているテンプレート構文を修正しようとするのではなく、既存のすべてのコードが壊れる可能性があるため、ほぼ不可能である可能性があるため、基本的に問題を隠すキーワードを追加します。基本的には「ハック」と呼ばれるものです。

標準のライブラリコンテナでautoを使用することに異論はありません。それは明らかにキーワードが作成された目的であり、標準ライブラリの関数は目的(またはその点では型)を根本的に変更する可能性が低く、autoを比較的安全に使用できます。しかし、私はそれをあなたのコードやインターフェースと一緒に使うことについては非常に用心深く、それらはより揮発性であり、潜在的にはより根本的な変更の影響を受ける可能性があります。

言語の機能を強化するautoのもう1つの便利なアプリケーションは、型にとらわれないマクロで一時を作成することです。これは、以前は実際にはできなかったことですが、今はできるのです。

19
Tim W

はい、autoを使用しない場合は、変数のタイプを簡単に知ることができます。問題は次のとおりです。コードを読み取るために変数の型を知るために、あなたは必要ですか?時には答えはイエス、時にはノーになります。たとえば、std::vector<int>からイテレータを取得する場合、それがstd::vector<int>::iteratorであることを知っている必要がありますか、それともauto iterator = ...;で十分ですか?イテレータで誰もがやりたいことはすべて、それがイテレータであるという事実によって与えられます-それは型が具体的に何であるかは関係ありません。

コードが読みにくくならないような状況では、autoを使用してください。

14

個人的に私はautoを使うのは、それが何であるかがプログラマーに絶対に明らかなときだけです。

例1

std::map <KeyClass, ValueClass> m;
// ...
auto I = m.find (something); // OK, find returns an iterator, everyone knows that

例2

MyClass myObj;
auto ret = myObj.FindRecord (something)// NOT OK, everyone needs to go and check what FindRecord returns
12
aleguna

この質問は、プログラマーごとに異なる意見を求めていますが、私はそうは言いません。実際、多くの場合、正反対です。autoは、プログラマが特徴点ではなくlogicに集中できるようにすることで、コードを理解しやすくするのに役立ちます。

これは、複雑なテンプレートタイプの場合に特に当てはまります。これは、単純化された人為的な例です。どちらが理解しやすいですか?

for( std::map<std::pair<Foo,Bar>, std::pair<Baz, Bot>, std::less<BazBot>>::const_iterator it = things_.begin(); it != things_.end(); ++it )

..または...

for( auto it = things_.begin(); it != things_.end(); ++it )

2番目の方が理解しやすいと言う人もいれば、1番目を言う人もいます。さらに、autoの不当な使用が、それを使用するプログラマの沈黙に寄与するかもしれないと言う人もいるかもしれませんが、それは別の話です。

10
John Dibling

これまでのところ、多くの良い答えがありますが、元の質問に焦点を当てると、ハーブは彼のアドバイスをやりすぎてautoを自由に使用することができないと思います。あなたの例は、autoを使用すると明らかに読みやすさが損なわれる1つのケースです。一部の人々は、変数にカーソルを合わせて型を見ることができる最新のIDEでは問題ないと主張しますが、私は同意しません:IDEを常に使用する人々でさえ、スニペットを見る必要がある場合がありますコードの分離(たとえば、コードレビューを考える)とIDEは役に立ちません。

結論:役立つ場合はautoを使用します。つまり、forループの反復子です。読者がタイプを見つけるのに苦労するときに使用しないでください。

8

明確なタイプがない場合、その自動機能がまだ誰も指摘していないことに私はかなり驚いています。この場合、テンプレートで#defineまたはtypedefを使用してこの問題を回避し、実際に使用可能なタイプを見つけるか(これは簡単ではない場合があります)、またはautoを使用します。

プラットフォーム固有のタイプの何かを返す関数を取得したとします。

#ifdef PLATFROM1
__int256 getStuff();
#else //PLATFORM2
__int128 getStuff();
#endif

魔女の使用法を好みますか?

#ifdef PLATFORM1
__int256 stuff = getStuff();
#else
__int128 stuff = getStuff();
#endif

または単に

auto stuff = getStuff();

確かに、あなたは書くことができます

#define StuffType (...)

同様にどこかですが

StuffType stuff = getStuff();

実際にxの型についてもっと何か教えて?そこから何が返されるかがわかりますが、それはまさにautoが何であるかです。これは冗長です-「もの」はここに3回書かれています-これは私の意見では「自動」バージョンよりも読みにくくします。

6
Lrdx

読みやすさは主観的です。状況を見て、何が最善かを判断する必要があります。

指摘したように、autoがないと、長い宣言は多くの混乱を生む可能性があります。しかし、あなたが指摘したように、短い宣言は価値のある型情報を取り除くことができます。

これに加えて、これも追加します。必ず、読みやすさではなく読みやすさに注目してください。書くのが簡単なコードは、一般的に読みにくく、その逆も同様です。たとえば、私が書いているなら、私は自動を好むでしょう。私が読んでいたなら、おそらくもっと長い宣言でしょう。

次に、一貫性があります。それはあなたにとってどのくらい重要ですか?一部の部分でautoを使用し、他の部分で明示的な宣言を使用しますか、それとも一貫した1つの方法を使用しますか?

3
user1161318

読みにくいコードの利点を活用し、プログラマーにますます使用するように勧めます。どうして?明らかに、autoを使用するコードが読みにくい場合は、書き込みも難しくなります。プログラマーは意味のある変数名を使用することを強制されますで、彼/彼女の仕事をより良くします。
たぶん初めにプログラマは意味のある変数名を書いていないかもしれません。しかし、最終的にはバグの修正中、またはコードレビューで、コードを他の人に説明する必要がある場合、または近い将来、保守担当者にコードを説明する場合、プログラマは間違いを認識し、使用します将来的に意味のある変数名。

2
Manoj R

2つのガイドラインがあります。

  • 変数の型が明らかである場合、作成するのが面倒であるか、使用を決定するのが難しいautoです。

    auto range = 10.0f; // Obvious
    
    for (auto i = collection.cbegin(); i != cbegin(); ++i) // Tedious if collection type
    // is really long
    
    template <typename T> ... T t; auto result = t.get(); // Hard to determine as get()
    // might return various stuff
    
  • 特定の変換が必要な場合、または結果のタイプが明確ではなく、混乱を招く可能性がある場合。

    class B : A {}; A* foo = new B(); // 'Convert'
    
    class Factory { public: int foo(); float bar(); }; int f = foo(); // Not obvious
    
2
Red XIII

はい。冗長性は低下しますが、冗長性は読みやすさを低下させるという一般的な誤解があります。これは、コードを解釈する実際の能力ではなく、読みやすさを美的であると考える場合にのみ当てはまります。これは、autoを使用しても増加しません。最も一般的に引用される例であるベクトル反復子では、autoを使用するとコードの可読性が向上することが表面に現れる場合があります。一方、autoキーワードによって何が得られるかは常にわかりません。コンパイラが内部再構築を行うために行うのと同じ論理パスに従う必要があります。多くの場合、特にイテレータでは、誤った仮定をすることになります。

結局のところ、「自動」はコードの可読性と明快さを犠牲にします。これは、構文と美的「クリーンさ」(イテレータが不必要に複雑な構文を持っているので必要なだけです)と、特定の行に10文字少ない文字を入力する機能です。リスクを冒す価値はありませんし、長期にわたる努力も必要ありません。

0
metamorphosis