Objective C++ 块语义

10

请考虑下面的C++方法:

class Worker{
....
private Node *node
};

void Worker::Work()
{
    NSBlockOperation *op=[NSBlockOperation blockOperationWithBlock:  ^{
            Tool hammer(node);
            hammer.Use();
          }];
    ....
    }

当块捕获“node”时,它究竟捕获了什么?关于块的语言规范,在其他情况下是明确的: http://clang.llvm.org/docs/BlockLanguageSpec.html

在复合语句的作用域内使用的变量以正常方式绑定到块上,但自动(堆栈)存储中的变量除外。因此,一个可以按照预期访问函数和全局变量,以及静态局部变量。[testme]

在块的复合语句中引用的本地自动(堆栈)变量会被导入并作为const副本被块捕获。

但是,在这里,我们是否捕获了this的当前值?使用Worker的复制构造函数的this的副本?还是对存储node的位置的引用?

特别是,假设我们说

 {
 Worker fred(someNode);
 fred.Work();
 }

当代码块运行时,对象fred可能已经不存在了。node的值是多少?(假设底层Node对象一直存在,但Web Workers随时可能消失。)

如果我们写成:

void Worker::Work()
    {
        Node *myNode=node;
        NSBlockOperation *op=[NSBlockOperation blockOperationWithBlock:  ^{
                Tool hammer(myNode);
                hammer.Use();
              }];
        ....
        }

结果不同吗?


考虑使用 C++11 lambdas 来获得更优秀的 C++ 类型捕获语义。 - justin
@justin:它有什么优越性吗?C++ lambda 也会通过值捕获 this 并具有完全相同的语义。 - newacct
@newacct 我从未写过“考虑使用C++11 lambda来获得更优秀的this捕获语义”。然而,一个区别在于lambda表达式不能隐式地捕获this,这是一种良好的保护措施。 - justin
2个回答

11
根据该页面

通常情况下,您可以在块中使用 C++ 对象。在成员函数内部,对于成员变量和函数的引用是通过一个隐式导入的 this 指针来实现的,因此它们看起来是可变的。如果要复制块,则有两个需要考虑的问题:

  • 如果您为原本会成为基于堆栈的 C++ 对象的 __block 存储类,则使用通常的复制构造函数。
  • 如果您在块中使用任何其他基于堆栈的 C++ 对象,则它必须具有 const 复制构造函数。然后使用该构造函数复制 C++ 对象。

根据经验观察,它将 this 指针作为 const 进行复制到块中。如果 this 指向的 C++ 实例在块执行时(例如,如果在更高的帧上堆栈分配了 Worker 实例上调用了 Worker::Work()),则会出现 EXC_BAD_ACCESS 或更糟糕的情况(即指针别名)。所以看起来是:

  • 它捕获的是 this,而不是通过值复制实例变量。
  • 没有任何操作来保持 this 指向的对象存活。

或者,如果引用在本地堆栈分配的(即在该堆栈帧/作用域中声明的)C++ 对象,则观察到其复制构造函数在最初被块捕获时被调用,然后在每次复制块时再次调用(例如,通过操作队列将操作排队时)。

针对您的问题:

但是,在这里,我们捕获了 this 的当前值吗?使用 Worker 的复制构造函数复制了这个吗?还是引用了存储 node 的位置?

我们捕获 this。如果这有帮助,请将其视为 intptr_t 的 const-copy。

当块被运行时,对象 fred 可能已经不存在了。此时 node 的值是多少?(假设底层的 Node 对象永远存在,但 Worker 会来来去去。)

在这种情况下,this 已被按值捕获,node 实际上是一个指针,其值为 this + <Worker 中 node 的偏移量>,但由于 Worker 实例已经消失,它实际上是一个垃圾指针。

我推断没有魔法或其他行为,除了那些文档中描述的行为。


1
在C++中,当您编写一个实例变量node时,如果没有明确编写something->node,则会隐含地使用this->node。(类似于Objective-C中,如果您编写一个实例变量node,而没有明确编写something->node,则隐含地使用self->node。)
因此,被使用的变量是this,并且它被捕获。(从技术上讲,this在标准中被描述为自己的单独表达式类型,而不是变量;但就所有目的而言,它作为类型为Worker *const的隐含局部变量。)与所有非__block变量一样,捕获它会创建thisconst副本。
当块捕获Objective-C对象指针类型的变量时,块具有内存管理语义。然而,this没有Objective-C对象指针类型,所以在内存管理方面不会对其进行任何操作。(无论如何,在C++内存管理方面也无法执行任何操作。)因此,是的,在块运行时,由this指向的C++对象可能无效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接