[dcl.attr.noreturn]は次の例を提供します:
[[ noreturn ]] void f() {
throw "error";
// OK
}
しかし[[noreturn]]
、関数の戻り値の型が既にあるので、私は何の意味があるのかわかりませんvoid
。
では、noreturn
属性のポイントは何ですか?どのように使用することになっていますか?
[dcl.attr.noreturn]は次の例を提供します:
[[ noreturn ]] void f() {
throw "error";
// OK
}
しかし[[noreturn]]
、関数の戻り値の型が既にあるので、私は何の意味があるのかわかりませんvoid
。
では、noreturn
属性のポイントは何ですか?どのように使用することになっていますか?
回答:
noreturn属性は、呼び出し元に戻らない関数に使用されることになっています。これはvoid関数(呼び出し元に戻る-値を返さない)を意味するのではなく、制御フローが関数の終了後に呼び出し元の関数に戻らない関数(たとえば、アプリケーションを終了する関数、永久にループするか、例のように例外をスローします)。
コンパイラはこれを使用して、いくつかの最適化を行い、より適切な警告を生成できます。たとえばf
、にnoreturn属性がある場合、コンパイラーは、g()
書き込み時にデッドコードであることを警告する可能性がありますf(); g();
。同様に、コンパイラーは、の呼び出し後にreturnステートメントが欠落していることについて警告しないことを知っていますf()
。
execve
そのような関数は返すべきではありませんが、できますか?noreturn属性が必要ですか?
noreturn
属性を持っていてはなりません。noreturn
など、アボートたとえば、あなたが終了し(呼び出すため)、()、アサート(0) -あなたの関数が呼び出し元に戻ることができ、制御フローの前にプログラムを終了何かをすることが保証されている場合にのみ使用することができる
noreturn
です。その例外の処理は、戻ったのと同じではありません。try
呼び出し後の内のコードはまだ到達不可能であり、到達できない場合void
、戻り値の割り当てや使用は行われません。
noreturn
関数が値を返さないことをコンパイラに伝えません。これは、制御フローが呼び出し元に戻らないことをコンパイラーに通知します。これにより、コンパイラーはさまざまな最適化を行うことができます-呼び出しの前後の揮発性の状態を保存および復元する必要はありません。そうでなければ、呼び出しに続くコードなどをデッドコードで排除できます。
関数が完了しないことを意味します。への呼び出し後、制御フローがステートメントにヒットすることはありませんf()
。
void g() {
f();
// unreachable:
std::cout << "No! That's impossible" << std::endl;
}
この情報は、コンパイラー/オプティマイザーによってさまざまな方法で使用できます。コンパイラーは、上記のコードに到達できないことを示す警告を追加できg()
ます。また、継続をサポートするなど、さまざまな方法で実際のコードを変更できます。
-Wno-return
と警告が表示されます。おそらくあなたが期待していたものではないかもしれませんが、おそらくコンパイラが何であるかを知ってい[[noreturn]]
て、それを利用できることを伝えるには十分でしょう。(私は少し驚いています-Wunreachable-code
...でキックしませんでした)
-Wmissing-noreturn
が、警告は、フロー解析によりにstd::cout
到達できないと判断されたことを意味します。私は、アセンブリ生成を見て、手で新しい十分なGCCを持っていませんが、呼び出しがする場合、私は驚かないだろうoperator<<
落とした
-O1
は、もう十分[[noreturn]]
です。
[[noreturn]]
はコードからを推測できます。この変換単位が、どこか別の場所で定義された関数の宣言しか持っていない場合、コンパイラーは関数が返らないことを知らないため、そのコードをドロップできません。これは、属性がコンパイラを支援する場所です。
以前の答えはノーリターンとは何かを正しく説明しましたが、なぜそれが存在しないのかではありませんでした。「最適化」コメントが主な目的だとは思いません。返らない関数はまれであり、通常は最適化する必要はありません。むしろ、ノーリターンの主な存在理由は、偽陽性の警告を回避することだと思います。たとえば、次のコードを考えてみます。
int f(bool b){
if (b) {
return 7;
} else {
abort();
}
}
abort()が「noreturn」とマークされていなかった場合、コンパイラーは、fが期待どおりに整数を返さないパスを持つこのコードについて警告した可能性があります。しかし、abort()はリターンなしとマークされているため、コードが正しいことがわかります。
タイプは理論的に言えば、void
他の言語で呼ばれているものですunit
かtop
。これに相当するものはTrueです。任意の値を正当にキャストできますvoid
(すべてのタイプはのサブタイプですvoid
)。それを「ユニバース」セットと考えてください。世界中のすべての値に共通する操作がないため、typeの値に対して有効な操作はありませんvoid
。言い換えれば、何かがユニバースセットに属していることを伝えると、情報はまったく提供されません-すでに知っています。したがって、以下は健全です。
(void)5;
(void)foo(17); // whatever foo(17) does
しかし、以下の割り当てはそうではありません:
void raise();
void f(int y) {
int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
cout << x << endl;
}
[[noreturn]]
一方、時々呼ばれempty
、Nothing
、Bottom
またはBot
との論理等価である偽。値はまったくなく、このタイプの式は、任意のタイプにキャストできます(つまり、そのサブタイプです)。これは空のセットです。誰かが「式foo()の値は空のセットに属している」と言った場合、それは非常に有益です-この式は通常の実行を完了しないことを示しています。中止、スロー、またはハングします。これはの正反対ですvoid
。
したがって、以下は意味がありません(noreturn
ファーストクラスのC ++型ではないため、疑似C ++)
void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns
ただし、以下の割り当てはthrow
、コンパイラによって返されないことが理解されているため、完全に正当です。
void f(int y) {
int x = y!=0 ? 100/y : throw exception();
cout << x << endl;
}
完璧な世界では、上記noreturn
の関数の戻り値として使用できますraise()
。
noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();
悲しいことに、C ++はそれを許可しません。おそらく実用上の理由からでしょう。代わりに、[[ noreturn ]]
コンパイラーの最適化と警告のガイドに役立つ属性を使用することができます。
void
たりvoid
評価しtrue
たりfalse
することはできません。
(void)true;
答えが示唆するように、完全に有効です。void(true)
構文的に完全に異なるものです。これは、引数としてvoid
コンストラクタを呼び出して、タイプの新しいオブジェクトを作成する試みtrue
です。これは、void
ファーストクラスではないため、他の理由の中でも失敗します。