13

GCC C ++コンパイラは、次のような関数属性を介して拡張機能のファミリを提供します。

int square(int) __attribute__((const));

特に2つの属性、constおよびpureは、関数の評価に副作用がなく、引数(const)のみ、または引数とグローバル変数(pure)のみに依存することを宣言できます。これにより、共通部分式の除去が可能になり、そのような関数がコードに記述されている回数よりも少なく呼び出されるという影響が生じる可能性があります。

私の質問は、これを仮想メンバー関数に安全に、正しく、そして賢明に使用できるかどうかです。

struct Foo
{
    virtual int square(int) __attribute__((pure));   // does that make sense?
};

これには意味のあるセマンティクスがありますか?まったく許されますか?それとも単に無視されますか?GCCのドキュメントでこれに対する答えを見つけることができないのではないかと思います。

この質問の理由は-Wsuggest-attribute、コードを改善するためにそれらの属性を配置できる場所の提案をGCCに生成させるコンパイラオプションのファミリがあるためです。ただ、仮想関数でもそういう提案をしてしまうようで、真剣に受け止めるべきではないかと思います。

4

4 に答える 4

5

最初の問題は、これらの属性が仮想メソッドに対して有効なセマンティクスを持っているかどうかです。私の意見では、そうです。仮想関数に純粋なラベルが付けられている場合、すべての実装が引数とグローバル メモリ内のデータのみに依存する (そしてそれを変更しない) ことをコンパイラに約束することになると思います。グローバル メモリ内のデータには、オブジェクト。仮想関数が const とラベル付けされている場合、これはその引数のみに依存できることを意味し、オブジェクトの内容を検査することさえ許可されません。コンパイラは、オーバーライドするすべての仮想メソッドが少なくとも親と同じくらい強力な属性を宣言するように強制する必要があります。

次の問題は、GCC がこれらの属性を使用して最適化を行うかどうかです。次のテスト プログラムでは、バージョン 4.6.3 がそうではないことがわかります (-O3 を指定してアセンブラーにコンパイルすると、ループが展開されることがわかります)。

struct A {
    virtual int const_f(int x) __attribute__((const)) = 0;
};

int do_stuff(A *a) {
    int b = 0;
    for (int i=0; i<10; i++) {
        b += a->const_f(0);
    }
    return b;
}

コンパイル時に型がわかっている次のプログラムでも、コンパイラはループを最適化しません。

struct A {
    virtual int const_f(int x) __attribute__((const)) = 0;
};

struct B : public A {
    int const_f(int x) __attribute__((const));
};

int do_stuff(B *b) {
    int c = 0;
    for (int i=0; i<10; i++) {
        c += b->const_f(0);
    }
    return c;
}

A から継承を削除する (したがってメソッドを非仮想にする) ことで、コンパイラは期待どおりの最適化を行うことができます。

これらの属性に関する標準やドキュメントはありません。そのため、入手できる最良のリファレンスは実装です。現在のところ効果がないため、将来動作が予期せず変更される場合に備えて、仮想メソッドでの使用を避けることをお勧めします。

于 2012-08-16T12:46:03.293 に答える
5

これは GCC によって許可され、受け入れられています。これは一般的に無視されません(GCC は常に を出力するためwarning: attribute ignored、属性を完全に無視すると、ここには表示されないため、これを知っています)。しかし、最後の段落も読んでください。

意味があるかどうかは別の問題です。仮想関数はオーバーロードでき、属性なしでオーバーロードできます。これにより、次の質問が開かれます。これは合法ですか?

異なる属性を持つ関数は、(const修飾子や異なる例外仕様などで) 異なるシグネチャーを持つことが期待されますが、そうではありません。GCC は、この点でそれらをまったく同じものとして扱います。これは、メンバ関数 non-constから派生Barして実装することで確認できます。Fooそれで

decltype(&Bar::square) f1 = &Foo::square;
decltype(&Foo::square) f2 = &Bar::square;

2 行目でコンパイル時エラーが発生しますが、1 行目では発生しません。シグネチャが異なっていた場合 (属性を使用する代わりに、関数を const 修飾してみてください!)、最初の行で既にエラーが発生していました。

最後に、それは安全で、理にかなっていますか? 常に安全です。コンパイラはそれを確認する必要があります。制限内で、意味的には意味があります。

セマンティックの観点から、関数を宣言することは「正しい」constpure、それが実際のものである場合です。ただし、インターフェイスのユーザーに「約束」をする限り、それは真実ではないかもしれません。誰かがこの関数を呼び出す可能性がありますが、これはすべての外観constに対して、これが真でない派生クラスにあります。コンパイラはそれが引き続き機能することを確認する必要がありますが、パフォーマンスに対するユーザーの期待は現実とは異なる場合があります。

const関数をまたpureは可能性としてマークすると、コンパイラはより適切に最適化できます。さて、仮想関数では、オブジェクトが派生型である可能性があるため、これはやや困難です。
これは、仮想呼び出しを静的に解決できない限り、コンパイラが最適化のために属性を無視する必要があることを意味します。これはまだよくあることかもしれませんが、一般的にはそうではありません。

于 2012-08-16T12:23:43.163 に答える
3

リンクしたドキュメントでは、const属性の説明の下に次のメモがあります。

ポインタ引数を持ち、ポイントされたデータを検査する関数は、constとして宣言してはならないことに注意してください。

メンバー関数には暗黙のポインターパラメーターがあるため、これにはメンバー関数が含まれていると思います(少なくとも、仮想関数はvtableに到達するためにそれを調べる必要がありますね)。

彼らはこのスレッドで同様の結論に達しているようです:http://gcc.gnu.org/ml/gcc/2011-02/msg00460.html

于 2012-06-12T12:31:32.820 に答える