4

ブロックの割り当ては、Objective-CクラスのパラメーターとC++クラスのパラメーターに関して異なる動作をすることがわかりました。

この単純なObjective-Cクラス階層があると想像してください。

@interface Fruit : NSObject
@end

@interface Apple : Fruit
@end

次に、次のようなものを書くことができます。

Fruit *(^getFruit)();
Apple *(^getApple)();
getFruit = getApple;

これは、Objective-Cクラスに関して、ブロックはその戻りタイプが共変であることを意味します。より具体的なものを返すブロックは、より一般的なものを返すブロックの「サブクラス」と見なすことができます。ここでgetAppleは、リンゴを届けるブロックを安全にブロックに割り当てることができますgetFruitApple *確かに、後で使用する場合は、を期待しているときに受け取るために常に保存されますFruit *。そして、論理的には、その逆は機能getApple = getFruit;しません。コンパイルされません。なぜなら、私たちが本当にリンゴが欲しいとき、私たちはただの果物を手に入れることに満足していないからです。

同様に、私はこれを書くことができます:

void (^eatFruit)(Fruit *);
void (^eatApple)(Apple *);
eatApple = eatFruit;

これは、ブロックが引数タイプで共変であることを示しています。より一般的な引数を処理できるブロックは、より具体的な引数を処理するブロックが必要な場合に使用できます。ブロックが果物の食べ方を知っていれば、リンゴの食べ方も知っています。繰り返しますが、その逆は真ではなく、これはコンパイルされません:eatFruit = eatApple;

これはすべてうまくいっています—Objective-Cでは。次に、これらの類似したC ++クラスがあると仮定して、C++またはObjective-C++でそれを試してみましょう。

class FruitCpp {};

class AppleCpp : public FruitCpp {};

class OrangeCpp : public FruitCpp {};

残念ながら、これらのブロック割り当てはコンパイルされなくなりました。

 FruitCpp *(^getFruitCpp)();
 AppleCpp *(^getAppleCpp)();
 getFruitCpp = getAppleCpp; // error!

 void (^eatFruitCpp)(FruitCpp *);
 void (^eatAppleCpp)(AppleCpp *);
 eatAppleCpp = eatFruitCpp; // error!

Clangは「互換性のないタイプからの割り当て」エラーで文句を言います。したがって、C ++クラスに関しては、ブロックは戻り型とパラメーター型で不変であるように見えます。

何故ですか?私がObjective-Cクラスで行ったのと同じ議論は、C ++クラスにも当てはまりませんか?私は何が欠けていますか?

4

2 に答える 2

11

Objective-CとC++のオブジェクトモデルには違いがあるため、この区別は意図的なものです。特に、Objective-Cオブジェクトへのポインターが与えられると、ポインターの値を実際に変更することなく、そのポインターを基本クラスまたは派生クラスを指すように変換/キャストできます。オブジェクトのアドレスは関係なく同じです。

C ++では複数の仮想継承が許可されているため、これはC ++オブジェクトには当てはまりません。C++クラスへのポインターがあり、そのポインターをキャスト/変換して基本クラスまたは派生クラスを指すようにすると、調整が必要になる場合があります。ポインタの値。たとえば、次のことを考慮してください。

class A { int x; }
class B { int y; }
class C : public A, public B { }

B *getC() { 
  C *c = new C;
  return c;
}

getC()の新しいCオブジェクトがアドレス0x10に割り当てられたとしましょう。ポインタ'c'の値は0x10です。returnステートメントでは、CへのポインタをC内のBサブオブジェクトを指すように調整する必要があります。BはCの継承リストでAの後に来るため、(通常は)Aの後にメモリに配置されます。ポインタへの4バイトのオフセット(== sizeof(A))であるため、返されるポインタは0x14になります。同様に、B*をC*にキャストすると、C内のBのオフセットを考慮して、ポインターから4バイトが減算されます。仮想基本クラスを処理する場合、考え方は同じですが、オフセットは不明であり、コンパイル時定数です。 :実行中にvtableを介してアクセスされます。

ここで、これが次のような割り当てに与える影響を考えてみましょう。

C (^getC)();
B (^getB)();
getB = getC;

getCブロックはCへのポインターを返します。Bへのポインターを返すブロックに変換するには、ブロックの各呼び出しから返されるポインターを4バイト追加して調整する必要があります。これはブロックの調整ではありません。これは、ブロックによって返されるポインター値の調整です。前のブロックをラップして調整を実行する新しいブロックを合成することで、これを実装できます。

getB = ^B() { return getC() }

これはコンパイラーで実装可能であり、調整が必要な共変リターン型を持つ仮想関数で仮想関数をオーバーライドするときに、同様の「サンク」がすでに導入されています。ただし、ブロックを使用すると、追加の問題が発生します。ブロックでは==との等価比較が可能になるため、「getB == getC」かどうかを評価するには、割り当て「getB=」によって生成されるサンクを調べる必要があります。 getC」を使用して、基になるブロックポインタを比較します。繰り返しになりますが、これは実装可能ですが、戻り値(および反変パラメーター)に対してこれらの調整を実行できる(一意の)サンクを作成できる、はるかに重いブロックランタイムが必要になります。これらはすべて技術的には可能ですが、コスト(実行時のサイズ、複雑さ、実行時間)がメリットを上回ります。

Objective-Cに戻ると、単一継承のオブジェクトモデルでは、オブジェクトポインターを調整する必要はありません。ポインターの静的なタイプに関係なく、特定のObjective-Cオブジェクトを指すアドレスは1つしかないため、共分散/逆分散サンクを必要とすることはなく、ブロックの割り当ては単純なポインターの割り当てです(ARCでは+ _Block_copy / _Block_release)。

于 2013-03-11T14:32:32.983 に答える
1

この機能はおそらく見落とされていました。Objective-CタイプのObjective-C++で共変性と反変性を機能させることに関心を持っているClangの人々を示すコミットがありますが、C++自体には何も見つかりませんでした。ブロックの言語仕様では、C++またはObjective-Cの共分散または反変性については言及されていません。

于 2013-03-10T21:36:44.753 に答える