auto
は、C++ 11に追加されるかなり重要な機能であり、多くの新しい言語に準拠しているようです。 Pythonのような言語と同様に、明示的な変数宣言を見たことはありません(Python標準を使用して可能かどうかはわかりません)。
変数を明示的に宣言する代わりにauto
を使用して変数を宣言することには欠点がありますか?
あなたは欠点について質問しただけなので、それらのいくつかを強調します。 auto
を適切に使用すると、いくつかの利点もあります。欠点は、悪用のしやすさ、およびコードが意図しない方法で動作する可能性が高まることに起因します。
主な欠点は、auto
を使用しても、作成されるオブジェクトのタイプが必ずしもわからないことです。また、プログラマーはコンパイラーが1つのタイプを推測することを期待するかもしれませんが、コンパイラーは別のタイプを断固として推測します。
のような宣言を与えられた
auto result = CallSomeFunction(x,y,z);
タイプresult
が何であるかを必ずしも知る必要はありません。 int
である可能性があります。ポインタである可能性があります。他の何かかもしれません。これらはすべて異なる操作をサポートしています。次のような小さな変更によってコードを劇的に変更することもできます
auto result = CallSomeFunction(a,y,z);
なぜなら、CallSomeFunction()
にどのオーバーロードが存在するかによって、結果のタイプが完全に異なる可能性があり、したがって後続のコードの動作が意図したものとは完全に異なる可能性があるためです。後のコードで突然エラーメッセージをトリガーする場合があります(たとえば、int
を逆参照しようとして、現在const
になっているものを変更しようとしています)。より不吉な変更は、あなたの変更がコンパイラを通過する場所ですが、後続のコードは異なる、未知の、おそらくバグのある方法で動作します。
したがって、一部の変数のタイプについて明示的な知識がないと、コードが意図したとおりに機能するという主張を厳密に正当化することが難しくなります。これは、重要度の高い(たとえば、セーフティクリティカルまたはミッションクリティカルな)ドメインで「目的に合っている」という主張を正当化するためのより多くの努力を意味します。
もう1つの、より一般的な欠点は、プログラマーがauto
を使用して、コードが何をしているのかを考えず、コードを正しくコンパイルするのではなく、コードを強制的にコンパイルすることです。
これは、正確には原則としてauto
の欠点ではありませんが、実際的には一部の人にとっては問題のようです。基本的に、一部の人々は、a)auto
を型の救世主として扱い、それを使用するときに脳を遮断するか、b)auto
が常に値型に推論することを忘れます。これにより、人々は次のようなことをします。
auto x = my_obj.method_that_returns_reference();
おっと、オブジェクトを深くコピーしただけです。多くの場合、バグまたはパフォーマンス障害のいずれかです。次に、他の方法でスイングすることもできます。
const auto& stuff = *func_that_returns_unique_ptr();
これで、ぶら下がり参照が得られます。これらの問題はauto
によって引き起こされるものではないため、それに対する正当な議論とは見なしません。しかし、最初に挙げた理由により、auto
はこれらの問題を(私の個人的な経験から)より一般的にしているようです。
人々が時間を調整し、分業を理解することを考えると、auto
は基礎となるタイプを推測しますが、それでも参照性と定数性について考えたいと思います。しかし、少し時間がかかります。
他の答えは、「変数の型が実際にはわからない」などの欠点に言及しています。これは、コード内のずさんな命名規則に大きく関係していると思います。インターフェイスに明確な名前が付けられている場合、正確なタイプをcareする必要はありません。確かに、auto result = callSomeFunction(a, b);
はあまり語りません。ただし、auto valid = isValid(xmlFile, schema);
は、その正確な型を気にせずにvalid
を使用するのに十分であることを示しています。結局、if (callSomeFunction(a, b))
だけでは、タイプもわかりません。他のサブエクスプレッションの一時オブジェクトでも同じです。したがって、これはauto
の本当の欠点とは思わない。
その主な欠点は、正確な戻り値の型がnot使用したいものであるということです In結果として、実際の戻り値の型は、実装/最適化の詳細として「論理」戻り値の型と異なる場合があります。式テンプレートは、代表的な例です。これがあるとしましょう:
SomeType operator* (const Matrix &lhs, const Vector &rhs);
論理的には、SomeType
がVector
であると予想されますが、コードではそのように扱う必要があります。ただし、最適化の目的で、使用している代数ライブラリが式テンプレートを実装する可能性があり、実際の戻り値の型は次のとおりです。
MultExpression<Matrix, Vector> operator* (const Matrix &lhs, const Vector &rhs);
現在、問題はMultExpression<Matrix, Vector>
がconst Matrix&
とconst Vector&
を内部的に格納する可能性が高いことです。完全な式が終了する前にVector
に変換されることを期待しています。このコードがあれば、すべて順調です:
extern Matrix a, b, c;
extern Vector v;
void compute()
{
Vector res = a * (b * (c * v));
// do something with res
}
ただし、ここでauto
を使用した場合、問題が発生する可能性があります。
void compute()
{
auto res = a * (b * (c * v));
// Oops! Now `res` is referring to temporaries (such as (c * v)) which no longer exist
}
欠点の1つは、auto
でconst_iterator
を宣言できない場合があることです。 この質問 から取られたコードのこの例では、通常の(非const)イテレーターを取得します。
map<string,int> usa;
//...init usa
auto city_it = usa.find("New York");
コードを読むのが少し難しく、または退屈になります。そのようなものを想像してください:
auto output = doSomethingWithData(variables);
ここで、出力のタイプを把握するには、doSomethingWithData
関数のシグネチャを追跡する必要があります。
this developerのように、auto
は嫌いです。むしろ、人々がauto
を悪用する方法が嫌いです。
私はauto
は一般的なコードの作成を支援するための(強い)意見であり、タイピングを減らすためではありません。
C++は、堅牢なコードnotを記述して開発時間を最小限に抑えることを目的とする言語です。
これは、C++の多くの機能からかなり明らかですが、残念ながらauto
のような新しいタイプのいくつかは、タイピングを怠り始めるべきだと誤解させます。
auto
より前の日、人々はtypedef
sを使用しました。これは、typedef
がライブラリの設計者に戻り型がどうあるべきかを理解してもらい、ライブラリが機能するためです。予想通り。 auto
を使用する場合、そのコントロールを取り去るクラスのデザイナーから、代わりにcompilerに問い合わせて、型がどうあるべきかを確認します。ツールボックスの最も強力なC++ツールとリスクbreakingのコード。
一般的に、auto
を使用する場合、コードはany合理的なタイプ、not動作する型を書き留めるのが面倒だからです。 auto
を怠asを助けるためのツールとして使用すると、通常、プログラムに微妙なバグが導入され始めます。 auto
を使用したために発生しなかった暗黙の変換によって。
残念ながらこれらのバグは説明が難しいここでの短い例では、簡潔さのためにユーザープロジェクトで出てくる実際の例よりも説得力が低くなるため、テンプレート中心のコードで簡単に発生します。特定の暗黙的な変換が行われることを期待するもの。
例が必要な場合は、1つ here があります。ただし、少し注意してください:コードをジャンプして批判したいと思う前に、多くの有名で成熟したライブラリがそのような暗黙の変換を中心に開発されていることを覚えておいてください。それらは問題を解決する不可能ではないとしても難しいかもしれませんそうでなければ解決します。それらを批判する前により良い解決策を理解するようにしてください。
auto
には欠点自体はありません。新しいコードのどこでも(手作業で)使用することを推奨します。これにより、コードで一貫した型チェックが行われ、サイレントスライスが一貫して回避されます。 (B
がA
から派生し、A
を返す関数が突然B
を返す場合、auto
は戻り値を格納するために期待どおりに動作します)
ただし、C++ 11より前のレガシーコードは、明示的に型指定された変数の使用によって引き起こされる暗黙的な変換に依存する場合があります。 明示的に型指定された変数をauto
に変更すると、コードの動作が変わる可能性がありますですので、注意が必要です。
キーワードauto
は、戻り値から型を推測するだけです。したがって、それはPythonオブジェクトと同等ではありません。
# Python
a
a = 10 # OK
a = "10" # OK
a = ClassA() # OK
// C++
auto a; // Unable to deduce variable a
auto a = 10; // OK
a = "10"; // Value of const char* can't be assigned to int
a = ClassA{} // Value of ClassA can't be assigned to int
a = 10.0; // OK, implicit casting warning
auto
はコンパイル中に推測されるため、実行時にいかなる欠点もありません。
ここまで誰も言及していませんが、あなたが私に尋ねたなら、それ自体が答えに値します。
(誰もがC != C++
に注意する必要がある場合でも)Cで記述されたコードは、C++コードのベースを提供するように簡単に設計できるため、C++と互換性を保つためにあまり労力をかけずに設計できるため、設計の要件になる可能性があります。
私はC
からのいくつかの明確に定義された構造がC++
に対して無効であり、その逆もあるいくつかのルールについて知っています。しかし、これは単に壊れた実行可能ファイルになり、既知のUB句が適用されます。ほとんどの場合、異常なループがクラッシュなどを引き起こします(または検出されないままである場合もありますが、ここでは関係ありません)。
しかし、auto
は初めてです1 これが変わる!
前にauto
をストレージクラス指定子として使用し、コードを転送したとします。 「使用」方法に応じて「ブレーク」する必要はありません。実際にプログラムの動作を静かに変更できます。
それは心に留めておくべきものです。
1少なくとも初めて知ったとき。
この回答auto
で説明したように、意図しないファンキーな状況になることがあります。 auto
がポインタ型を作成できるのと同時に、参照型を持つためにauto&
を明示的に言う必要があります。これにより、指定子をすべて省略すると混乱が生じ、実際の参照の代わりに参照のコピーが作成されます。
私が考えることができる1つの理由は、返されるクラスを強制する機会を失うことです。関数またはメソッドが長い64ビットを返し、32の符号なし整数のみが必要な場合、それを制御する機会を失います。
別の刺激的な例:
for (auto i = 0; i < s.size(); ++i)
i
は符号付き整数であるため、警告(comparison between signed and unsigned integer expressions [-Wsign-compare]
)を生成します。これを回避するには、たとえば.
for (auto i = 0U; i < s.size(); ++i)
またはおそらくより良い:
for (auto i = 0ULL; i < s.size(); ++i)
auto
はローカライズされたコンテキストで使用すると良いと思います。そこでは、読者がそのタイプを簡単かつ明らかに推測できます。それがどのように機能するかを理解していない人は、template
または同様の代わりに使用するなど、間違った方法でそれを使用する可能性があります。私の意見では、ここにいくつかの良いユースケースと悪いユースケースがあります。
void test (const int & a)
{
// b is not const
// b is not a reference
auto b = a;
// b type is decided by the compiler based on value of a
// a is int
}
適切な使用
イテレータ
std::vector<boost::Tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int> v();
..
std::vector<boost::Tuple<ClassWithLongName1,std::vector<ClassWithLongName2>,int>::iterator it = v.begin();
// VS
auto vi = v.begin();
関数ポインタ
int test (ClassWithLongName1 a, ClassWithLongName2 b, int c)
{
..
}
..
int (*fp)(ClassWithLongName1, ClassWithLongName2, int) = test;
// VS
auto *f = test;
不適切な使用
データフロー
auto input = "";
..
auto output = test(input);
関数シグネチャ
auto test (auto a, auto b, auto c)
{
..
}
些細なケース
for(auto i = 0; i < 100; i++)
{
..
}