2013-03-09 70 views
4

我發現賦值塊在Objective-C類參數和C++類參數方面的行爲不同。分配塊指針:Objective-C和C++類之間的區別

想象我有這個簡單的Objective-C類層次結構:

@interface Fruit : NSObject 
@end 

@interface Apple : Fruit 
@end 

然後,我可以寫這樣的東西:

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

這意味着,對於Objective-C類 ,塊返回類型爲協變:返回更具體的東西的塊可以看作是返回更一般東西的塊的「子類」。在這裏,提供蘋果的getApple塊可以安全地分配給getFruit塊。事實上,如果以後使用,當您預計Fruit *時,它總是保存爲接收Apple *。而且,從邏輯上講,逆向不起作用:getApple = getFruit;不能編譯,因爲當我們真的想要一個蘋果時,我們並不開心得到一個水果。

同樣,我可以寫:

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

這表明塊在它們的參數類型協變:能夠處理一個參數,更一般的,可以使用一個塊,其中一個塊的過程需要更具體的論點。如果一個街區知道如何吃水果,它也會知道如何吃蘋果。再一次,反過來是不正確的,這不會編譯:eatFruit = eatApple;

這是一切都很好,在Objective-C中。現在讓我們嘗試在C++或Objective-C++,假設我們有這些類似的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! 

鏘抱怨與「從不兼容的類型分配「錯誤。所以,相對於C++類,塊看起來是不變的返回類型和參數類型

這是爲什麼?我對Objective-C類所做的論述是否也適用於C++類?我錯過了什麼?

+0

最有可能的是,該功能被忽略了。有[提交](http://llvm.org/viewvc/llvm-project?view=revision&revision=125445)顯示鏗鏘人關心在Objective-C類型的Objective-C++中爲協變和逆變工作,但我不能'爲C++本身找到任何東西。 [語言規範塊](http://clang.llvm.org/docs/BlockLanguageSpec.html)也沒有提及。 – zneak 2013-03-09 16:18:14

+0

我應該把它作爲一個錯誤/功能請求放在什麼地方? – 2013-03-09 16:23:04

+0

您可以提交LLVM項目的錯誤和功能請求[此處](http://llvm.org/bugs/enter_bug.cgi)(需要使用有效的電子郵件進行免費註冊,就像大多數公衆的錯誤跟蹤程序一樣),但希望延遲至少有幾個月的時間。如果你真的瞭解它,如果你想自己製作補丁,郵件列表上的人可能會很樂意協助你。 – zneak 2013-03-09 16:46:10

回答

10

由於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 *會從指針中減去4個字節,以解決C內部的B偏移量。當處理虛擬基類時,這個想法是相同的,但偏移不再是已知的,編譯時常量:它們在執行期間通過vtable訪問。

現在,考慮這對像賦值的效果:

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

的GETC塊返回一個指向下把它變成一個返回一個指向塊b,我們需要通過添加4個字節來調整每次調用塊返回的指針。這不是對該塊的調整;這是對塊返回的指針值的調整。人們可以通過合成一個新的塊,它包裝之前的塊,並執行調整,例如實現這一點,

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

這是在編譯器中,覆蓋一個虛函數時已經引入了類似的「的thunk」可實現該有一個需要調整的協變返回類型。但是,使用塊會導致另一個問題:塊允許與==進行相等比較,因此爲了評估「getB == getC」,我們必須能夠查看通過賦值生成的thunk「getB = getC「來比較底層的塊指針。再說一遍,這是可以實現的,但是需要一個更重的塊運行時,它能夠創建能夠對返回值(以及任何逆變參數)執行這些調整的(獨特)thunks。雖然所有這些在技術上是可行的,但成本(運行時間的大小,複雜性和執行時間)大於好處。回到Objective-C,單繼承對象模型從不需要對對象指針進行任何調整:不管指針的靜態類型如何,只有一個地址指向給定的Objective-C對象,所以協變/反變換永遠不需要任何thunk,並且塊分配是一個簡單的指針分配(ARC下的+ _Block_copy/_Block_release)。

+0

感謝您的深入瞭解和詳細的解答! – 2013-03-11 14:39:19

1

該功能可能被忽略。有commits顯示鏗鏘人關心在Objective-C類型的Objective-C++中使協變和反變換工作,但我無法找到C++本身的任何東西。 language specification for blocks沒有提到C++或Objective-C的協變或協變性。