メンバー関数の修飾子を制限します (このポインターを制限します)
-
25-10-2019 - |
質問
注記: 明確にしておきますが、質問はの使用に関するものではありません。 restrict
キーワード全般について説明しますが、特に説明したメンバー関数への適用について説明します。 ここ.
gcc を使用すると、 __restrict__
(C99 に相当する GNU++ restrict
) メンバー関数の修飾子を使用して、効果的に this
関数のスコープ内の制限付き修飾ポインター。牛肉はどこにありますか?
ほとんどのメンバー関数は他のメンバーに対して動作し、次の方法でアクセスします。 this
, 、 これは T* const
(通常はエイリアスなし)。のために this
エイリアス化される可能性があるためには、何らかの方法でメンバー関数内で使用される 2 番目の型へのポインターが必要であり、それはどこかから取得される必要があります。
これは、たとえばすべての二項演算子や、同一で自明ではない型の少なくとも 2 つのポインターまたは参照を取るその他の自由関数などの非メンバー関数でよく見られる状況です。ただし、これらの関数には this
, したがって、それらは関係ありません。
代入演算子、コピー コンストラクター、単項比較演算子はメンバー関数の例です。 this
できた 原則としてエイリアス化されます (別のオブジェクトが参照経由で渡されるため)。したがって、これらに制限修飾子を割り当てることだけが本当に意味があります。コンパイラにとっては、他のすべての関数がとにかく制限プロパティを持つことがすでに明らかであるはずです (2 番目の T へのポインタが存在しないため)。
さて、たとえば、次のように使用した場合 restrict
の上 operator=
あなたがすべき 結果的に 自己割り当てをまったくチェックしないでください、なぜならあなたはそれを言っているからです this
その関数のスコープ内でエイリアスが設定されていません (そして それが本当なら, 、自己割り当ては起こり得ません)。
もちろん、これは事前に知ることはできませんし、意味がありません。
では、実際にメンバー関数に制限修飾子を与えたい場合と、それが理にかなっている場合はどのような場合でしょうか?
解決
何かが足りないか、あなたの質問が意味をなさないかのどちらかです。 this
ではありません それ メンバー関数への他の引数とは異なるのに、GCC で適用できることになぜ驚いたのでしょうか。 restrict
それに?
代入演算子への適用に関しては、明示的な自己代入テストの必要性がなくなるとの指摘は正しくあります。それからあなたはこう言います:
明らかに、これは事前に知ることはできません
しかし、これは いつも 使用する場合は true restrict
何でも。たとえば、誰かが電話をかけることにしたとします。 memcpy
重複するメモリ領域。彼らがそうしないことを「事前に知ることはおそらく不可能」です。しかし restrict
の引数の宣言 memcpy
手段 そうした場合、彼らはエラーを犯したことになります. 。まったく同じように、代入演算子を宣言すると、 restrict
, 、誰かがそのクラスのオブジェクトを自己割り当てするのは間違いです。これについては、不思議なことも矛盾することもまったくありません。それはセマンティクスの一部にすぎません restrict
コードの残りの部分に特定の制約を課すことになります。
また、メンバー関数が同じ型の別のオブジェクトへのポインター (または参照) を受け取ることがなぜ不可能であるのかもわかりません。些細な例:
class Point {
public:
double distance(const Point &other) const;
};
この種のことは常に発生します。
それで本当の質問は、なぜそう思うのかということです this
他の議論とそんなに違うの?それとも、どうして私はあなたの要点を完全に見逃してしまったのでしょうか?
他のヒント
私はあなたたちが欠けていることを信じています、メンバー関数への議論も別名である可能性があるということです 部品 またはオブジェクト。これが例です
struct some_class {
int some_value;
void compute_something(int& result) {
result = 2*some_value;
...
result -= some_value;
}
}
おそらくそれがコンパイルされることを期待するでしょう
*(this + offsetof(some_value)) -> register1
2*register1 -> register2
...
register2 - register1 -> result
残念ながら、そのコードはそうでしょう 違う 誰かが結果のためにsome_valueへの参照を渡す場合。したがって、コンパイラは実際にフォローするために生成する必要があります
*(this + offsetof(some_value)) -> register1
2*register1 -> register2
register2 -> result
...
*(this + offsetof(some_value)) -> register1
result -> register2
register2 - register1 -> register2
register2 -> result
これは明らかに効率が低くなります。 compute_somethingがインラインでない限り、コンパイラは持っていることに注意してください 番号 結果がsome_valueのエイリアスが可能かどうかを知る方法 もっている 最悪のケースを想定するために、賢くても愚かでもありません。したがって、このポインターに適用されたとしても、制限するための明確で非常に現実的な利点があります。
あなたが投稿したリンクは興味深いです。持っているための確固たるユースケースはありません restrict
適用されます this
. 。あなたがあなたの質問で述べたように、 コンストラクター、operator =をコピーします 潜在的な候補者だったかもしれません。しかし、コンパイラはそれらの世話をすることができます。
しかし、次のケースは興味深い場合があります
struct A
{
//...
void Destroy (A*& p) __restrict__
{
delete this;
p = 0;
p++;
}
};
これで、ユースケースは次のとおりです。
A **pp = new A*[10];
for(int i = 0; i < 10; i++)
pp[i] = new A;
//...
A* p = pp[0];
for(int i = 0; i < 10; i++)
p->Destroy(p);
delete[] pp;
これは非常に珍しい慣行ですが、これは私がこのユースケースについて考えることができる唯一のケースです。
なぜあなたが話しているのかはっきりと理解していないのではないかと思います this
.
restrict
ポインターが他の人と重複していないことを指定します。したがって、コンパイラは、制限ポインターが依存していないことによって指摘されたメモリ領域が依存しないと仮定できます。これにより、より積極的な最適化が可能になります。 __restrict__
他のポインター変数に使用する場合よりもはるかに効果的です this
.
それで、実際にメンバーに制限予選を与えたい場合、そしてそれが理にかなっている場合はどうなりますか?
使用の代表的なケースを思い出してください restrict
ポインター memcpy
:
void Foo::MyCompute(__restrict__ char* bufA, __restrict__ char* BufB)
{
}
これを答えとして追加します。なぜなら、それはおそらくそのように適しているより良いからです(それは一種の答えであり、実際には質問に属していないことに加えて、コメントには少し長いです)。
長い間ネモの答えについて考えた後、私は自己割り当てに関する私たちの両方の解釈が多少間違っているかもしれないと信じています(ただし、ネモは私のものよりも正しいものでしたが)。 Nemoが正しく指摘したように、制限資格がありました this
実際、エイリアシングの存在はプログラムエラーであることを意味します。これ以上、それ以下。
これを書くとき、あなたの論理は実際に「これが起こらないと言うので、結果として自己割り当てをチェックすべきではない」ではなく、「エイリアシングは起こらないと言っているので、それがプログラムのエラーであると言うので、あなたは明示的に言うので、あなた だけでなく 自己割り当てを確認する必要がありますが、結果として しなければならない それが起こった場合、激しく失敗します」。
そして、それは特定のプログラムロジックを強調し、同時にコンパイラがその特定の特別なケースに対してより良く最適化できるようにするため、したがって 確かに理にかなっています.