これはauto
、C ++ 11に追加されるかなり重要な機能であり、多くの新しい言語に従っているようです。Pythonのような言語と同様に、明示的な変数宣言はありません(Python標準を使用して可能かどうかはわかりません)。
auto
変数を明示的に宣言する代わりにを使用して宣言することに欠点はありますか?
これはauto
、C ++ 11に追加されるかなり重要な機能であり、多くの新しい言語に従っているようです。Pythonのような言語と同様に、明示的な変数宣言はありません(Python標準を使用して可能かどうかはわかりません)。
auto
変数を明示的に宣言する代わりにを使用して宣言することに欠点はありますか?
回答:
あなたは欠点についてのみ尋ねてきたので、それらのいくつかを強調します。うまく使用すると、auto
いくつかの利点もあります。欠点は、乱用のしやすさと、コードが意図しない方法で動作する可能性が高まることに起因します。
主な欠点は、を使用してもauto
、作成されるオブジェクトのタイプが必ずしもわかるとは限らないことです。また、プログラマーがコンパイラーがあるタイプを推定することを期待するかもしれませんが、コンパイラーは固く別のタイプを推定する場合もあります。
のような宣言が与えられた
auto result = CallSomeFunction(x,y,z);
必ずしもタイプresult
が何であるかを知っている必要はありません。かもしれませんint
。ポインタかもしれません。それは何か他のものかもしれません。これらはすべて、さまざまな操作をサポートしています。次のような小さな変更でコードを劇的に変更することもできます
auto result = CallSomeFunction(a,y,z);
CallSomeFunction()
結果のタイプに存在するオーバーロードによっては、完全に異なる可能性があるため、後続のコードは意図したものとはまったく異なる動作をする可能性があるためです。後のコードでエラーメッセージを突然トリガーする可能性があります(たとえば、後でを逆参照しようとするint
、現在のを変更しようとするconst
)。より不吉な変更とは、変更がコンパイラを通過するところですが、後続のコードは異なる方法で動作します。
したがって、一部の変数のタイプを明確に把握していないと、コードが意図したとおりに機能するという主張を厳密に正当化することが難しくなります。これは、重要性の高い(たとえば、安全性が重要な、またはミッションクリティカルな)ドメインで「目的に合っている」という主張を正当化するためのさらなる努力を意味します。
もう1つのより一般的な欠点はauto
、コードが何をしているのかを考えて正しく理解するのではなく、プログラマーが率直な手段としてコードを強制的にコンパイルさせる誘惑です。
auto
、ほとんどのアヒル型言語はそのような欠点を設計上抱えていることに注意するのは興味深いことです。
CallSomeFunction()
引数のシーケンスに応じて異なるタイプを返す場合、それはの設計上の欠陥でCallSomeFunction()
あり、の問題ではありませんauto
。使用する前に使用している関数のドキュメントを読んでいない場合、それはプログラマーの欠陥であり、の問題ではありませんauto
。-しかし、あなたはここで悪魔の支持者を演じていると理解しています。
T CallSomeFunction(T, int, int)
設計に欠陥があるのですか?明らかに、「引数のシーケンスに応じて異なる型を返します」。
auto
、作成されるオブジェクトのタイプが必ずしもわかるとは限らないことです。」なぜこれがの問題でありauto
、部分式の一時の問題ではないのか、詳しく説明できますか?なぜauto result = foo();
悪いのfoo().bar()
ですか?
これはauto
原理的には欠点ではありませんが、実際には一部の人にとって問題となるようです。基本的に、一部の人々は次のいずれかを行います:a)auto
タイプの救世主として扱い、それを使用するときに脳を遮断するか、またはb)auto
常に値タイプを推測することを忘れます。これにより、人々はこのようなことをします:
auto x = my_obj.method_that_returns_reference();
おっと、オブジェクトをディープコピーしただけです。多くの場合、バグかパフォーマンスの失敗です。次に、反対方向にも振ることができます。
const auto& stuff = *func_that_returns_unique_ptr();
これで、ぶら下がっている参照を取得します。これらの問題はまったく原因ではないauto
ので、それらに対する正当な議論とは見なしません。しかしauto
、冒頭に挙げた理由により、(個人的な経験から)これらの問題がより一般的になるように思われます。
人々が時間を調整し、分業を理解することを考えるauto
と、基になるタイプを推定しますが、参照性と定数性についてはまだ考えたいと思います。しかし、少し時間がかかります。
std::vector
)。コピーにコストがかかるのはクラスのプロパティではなく、個々のオブジェクトのプロパティです。したがってmethod_that_returns_reference
、コピーコンストラクターを持つクラスのオブジェクトを参照する可能性がありますが、どのオブジェクトをコピーするのが非常にコストがかかります(移動できない)。
std::vector
?(可能性があります、そう、またはクラスを制御しないためですが、それはポイントではありません)コピーするのにコストがかかる場合(そしてコピー可能であるためリソースを所有しない場合)、オブジェクトでCOWを使用しないのはなぜですか?オブジェクトのサイズにより、データの局所性はすでに失われています。
= delete
、その過負荷です。より一般的には、あなたが言うことは解決策です。これは、興味があれば私が調べたトピックです:nirfriedman.com/2016/01/18/…。
他の回答は、「変数の型が何であるか本当にわからない」のような欠点について言及しています。これは主に、コードのずさんな命名規則に関連していると思います。インターフェースに明確な名前が付けられている場合は、正確なタイプを気にする必要はありません。わかりauto result = callSomeFunction(a, b);
ません。しかし、その正確なタイプが何であるかを気auto valid = isValid(xmlFile, schema);
にするvalid
ことなく使用するのに十分です。結局のところ、だけif (callSomeFunction(a, b))
では、型もわかりません。他の部分式一時オブジェクトと同じです。だから私はこれを本当の欠点とは考えていませんauto
。
その主な欠点は、正確な戻り値の型が操作したいものと異なる場合があることです。実際、実際の戻り値の型は、実装/最適化の詳細として「論理」戻り値の型と異なる場合があります。式テンプレートはその代表的な例です。これがあるとしましょう:
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
完全な式の終わりの前にa に変換されることを期待しています。このコードがあれば、問題はありません。
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
}
auto
は欠点がほとんどないと私は感じています。また、プロキシなどの他の例には、DSLにあるさまざまな「文字列ビルダー」や類似のオブジェクトが含まれます。
auto
以前に、特にEigenライブラリーを使用して、式テンプレートに噛まれました。問題はデバッグビルドに表示されないことが多いため、特に注意が必要です。
auto
ある種の型チェックが含まauto
れていません(また、あらゆる場所でスプラッシュすると、他の場所と同じように型の安全性が失われます)。それは良い比較ではありません。
欠点の1つは、時にはあなたが宣言することができないということであるconst_iterator
とauto
。この質問から取ったこのコードの例では、通常の(非const)イテレータを取得します。
map<string,int> usa;
//...init usa
auto city_it = usa.find("New York");
iterator
あなたの地図はでないので、あなたはどんな場合でもを取得しますconst
。これをに変換する場合は、const_iterator
通常どおり変数の型を明示的に指定するか、メソッドを抽出して、マップがのコンテキストでconstになるようにしますfind
。(私は後者を好みます
auto city_it = static_cast<const auto&>(map).find("New York")
?または、C ++ 17では、auto city_if = std::as_const(map).find("New York")
。
それはあなたのコードを読むのを少し難しくするか退屈なものにします。そのようなものを想像してみてください:
auto output = doSomethingWithData(variables);
ここで、出力のタイプを把握するには、doSomethingWithData
関数の署名を追跡する必要があります。
auto it = vec.begin();
よりもはるかに読みやすいstd::vector<std::wstring>::iterator it = vec.begin();
です。
同じように、この開発者、私は嫌いauto
。むしろ、私は人々がどのように誤用するかを嫌いますauto
。
タイピングを減らすためauto
ではなく、一般的なコードを書くのを助けるための(強い)意見です。
C ++は、堅牢なコードを記述できるようにすることを目的とする言語であり、開発時間を最小限に抑えることはできません。
これはC ++の多くの機能からかなり明白ですが、残念ながら、auto
タイピングを減らすような新しい機能のいくつかは、人々がタイピングに怠惰になり始めるべきだと誤解させることがあります。
以前auto
は人々がtypedef
sを使用していましたが、これはすばらしいことtypedef
でした。ライブラリの設計者が戻り値の型を理解できるようにして、ライブラリが期待どおりに機能するようにしたからです。を使用する場合auto
、そのコントロールをクラスのデザイナーから取り除き、代わりにコンパイラーに型を特定するように依頼します。これにより、最も強力なC ++ツールの1つがツールボックスから削除され、コードが破損するおそれがあります。
あなたが使用している場合、一般的に、auto
ので、それがあるべきあなたのコードは、のために働くすべての合理的なタイプ、ではないあなたはそれがで動作するはずというタイプを書き留めてあまりにも怠惰だから。auto
怠惰を助けるためのツールとして使用する場合、通常はを使用したために発生しなかった暗黙の変換が原因で、プログラムに微妙なバグが発生し始めますauto
。
残念ながら、これらのバグは、説明することは困難ですが、彼らは一定の期待テンプレート重いコードで発生しやすい-その簡潔さは、それらが少ないユーザーのプロジェクトに出てくる実際の例よりも説得力になるので、ここでは簡単な例では暗黙の型変換を取るために場所。
例が必要な場合は、ここにあります。ただし、少し注意してください。コードをジャンプして批判する前に、そのような暗黙の変換を中心に多くのよく知られた成熟したライブラリが開発されており、不可能ではないにせよ困難な問題を解決するために存在しています。そうでなければ解決する。それらを批判する前に、より良い解決策を理解するようにしてください。
which was great because typedef allowed the designer of the library to help you figure out what the return type should be, so that their library works as expected. When you use auto, you take away that control from the class's designer and instead ask the compiler to figure out what the type should be
IMOの理由はあまりよくありません。たとえば、Visual Studio 2015などの最新のIDEでは、にカーソルを合わせると変数の型を確認できますauto
。これは、*正確に*と同じtypedef
1。
typename std::iterator_traits<It>::value_type
。(2)全体の要点は、推論された型は、前のコード設計者が意図した正しい型と「完全に同じ」である必要はないということでした。を使用auto
することにより、正しいタイプを指定するデザイナーの機能を奪います。
vector<bool>
ナンセンス" ...許し?どのようにbitset
実装されていると思いますか?それとも、ビットコンテナーはまったくナンセンスだと思いますか?!
auto
それ自体には欠点がありません。私は新しいコードのどこでも(手で)使用することを推奨します。これにより、コードで一貫して型チェックを行い、サイレントスライスを常に回避できます。(B
から派生しA
、関数がA
突然戻るB
場合、auto
期待どおりに動作して戻り値を格納します)
ただし、C ++ 11より前のレガシーコードは、明示的に型指定された変数の使用によって引き起こされる暗黙的な変換に依存する場合があります。明示的に型指定された変数をにauto
変更すると、コードの動作が変わる可能性があるため、注意が必要です。
auto
それ自体に欠点があります(または少なくとも-多くの人がそうだと思います)。中2番目の質問に与えられた例を検討し、このパネルディスカッションサッター、アレキとマイヤーズでは:あなたが持っている場合auto x = foo(); if (x) { bar(); } else { baz(); }
とfoo()
リターンbool
-あれば何が起こるfoo()
変化は(3つのオプションの代わりに、2)列挙型を返すために?auto
コードは動作し続けますが、予期しない結果を生成します。
bool
をauto
変更する代わりにを使用しますか?私は間違っているかもしれません(ここでは確認できません)が、唯一の違いはbool
、の条件の評価時ではなく、変数宣言時に変換が行われることif
です。enum
がスコープ指定されている場合、への変換bool
は明示的な通知なしには行われません。
キーワードは、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
はコンパイル時に推定されるため、実行時に何の欠点もありません。
type()
にpythonで何をするかを行います。型を推定し、その型の新しい変数を作成しません。
decltype
。auto
特に変数割り当て用です。
これまで誰もここで述べたことはありませんが、あなたが私に尋ねた場合、それ自体は答える価値があります。
C != C++
Cで記述されたコードは、C ++コードのベースを提供するように簡単に設計でき、C ++互換性を維持するためにそれほど労力を費やすことなく設計できるので(だれもが知っているはずですが)、これは設計の要件になる可能性があります。
私は、いくつかの明確に定義された構成C
が無効でC++
あり、その逆もあるいくつかのルールについて知っています。しかし、これは単に実行可能ファイルが壊れることになり、既知のUB節が適用されます。ほとんどの場合、奇妙なループによってクラッシュまたは何が発生するかが検出されます(または検出されないままになることもありますが、ここでは問題ではありません)。
しかしauto
、これが初めて変更されるのは1です!
auto
以前にストレージクラス指定子として使用して、コードを転送したとします。それは必ずしも(それが使用された方法に応じて)「壊れる」とは限りません。実際には、プログラムの動作を黙って変更する可能性があります。
それは心に留めておくべきことです。
1 少なくとも私が初めて知ったとき。
int
Cで「型が意味しない」に依存している場合、これから得られるすべての悪いものに値します。そして、それに依存していない場合、型と一緒にauto
ストレージクラス指定子として使用すると、 C ++で素晴らしいコンパイルエラーが発生します(この場合、これは良いことです)。
この回答で 説明したように、auto
意図しないファンキーな状況になることがあります。ポインタ型を作成できるauto&
だけで、参照型があると明示的に言わなければなりませんauto
。これにより、指定子がすべて省略されて混乱が生じ、実際の参照ではなく参照のコピーが作成される可能性があります。
auto
あり、参照やconst
型を推測することはありません。auto
言及、あなたはより良い利用をいただきたいですauto&&
。(汎用参照)型が安価にコピーできない、またはリソースを所有している場合、そもそも型はコピー可能であってはなりません。
別の苛立たしい例:
for (auto i = 0; i < s.size(); ++i)
は署名付き整数comparison between signed and unsigned integer expressions [-Wsign-compare]
であるため、警告()を生成しますi
。これを回避するには、たとえば、
for (auto i = 0U; i < s.size(); ++i)
またはおそらくより良い:
for (auto i = 0ULL; i < s.size(); ++i)
size
はsize_t
、戻り値を想定して、size_t
のようなリテラルを持つ必要があり0z
ます。ただし、これを行うにはUDLを宣言できます。(size_t operator""_z(...)
)
unsigned
すべての値を保持するのに十分なほど大きくない可能性が非常に高いため、非常に膨大std::size_t
な数の要素を持つコンテナが誰かにあるというまれな場合、使用unsigned
すると、低い範囲で無限ループが発生する可能性がありますインデックスの。これが問題になることはほとんどありませんが、std::size_t
意図を適切に伝えるクリーンなコードを取得するために使用する必要があります。unsigned long long
実際にはそれはおそらく同じであるに違いないが、それで十分であることが厳密に保証されているかどうかはわかりません。
unsigned long long
少なくとも64ビットであることが保証されていますが、理論的にsize_t
はこれよりも大きくなる可能性があると思います。もちろん、コンテナーに2 ^ 64を超える要素がある場合、心配する必要のある大きな問題が発生する可能性があります... ;-)
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++)
{
..
}
int
、さらに1文字入力する必要がありますauto
。それは受け入れられない
int
ここで簡単に見られるもので、タイピングint
はより短くなります。だから、それは些細なことです。
誰もこれについて言及していないことに驚いていますが、何かの階乗を計算しているとします。
#include <iostream>
using namespace std;
int main() {
auto n = 40;
auto factorial = 1;
for(int i = 1; i <=n; ++i)
{
factorial *= i;
}
cout << "Factorial of " << n << " = " << factorial <<endl;
cout << "Size of factorial: " << sizeof(factorial) << endl;
return 0;
}
このコードはこれを出力します:
Factorial of 40 = 0
Size of factorial: 4
それは明らかに期待される結果ではありませんでした。これauto
は、変数階乗の型int
がに割り当てられたためと推定されたために発生しました1
。