2013-03-09 42 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的协变或协变性。

相关问题