Question

Consider the following C++ method:

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

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

What, exactly, does the block capture when it captures "node"? The language specification for blocks, http://clang.llvm.org/docs/BlockLanguageSpec.html, is clear for other cases:

Variables used within the scope of the compound statement are bound to the Block in the normal manner with the exception of those in automatic (stack) storage. Thus one may access functions and global variables as one would expect, as well as static local variables. [testme]

Local automatic (stack) variables referenced within the compound statement of a Block are imported and captured by the Block as const copies.

But here, do we capture the current value of this? A copy of this using Worker’s copy constructor? Or a reference to the place where node is stored?

In particular, suppose we say

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

The object fred may not exist any more when the block gets run. What is the value of node? (Assume that the underlying Node objects live forever, but Workers come and go.)

If instead we wrote

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

is the outcome different?

Was it helpful?

Solution

According to this page:

In general you can use C++ objects within a block. Within a member function, references to member variables and functions are via an implicitly imported this pointer and thus appear mutable. There are two considerations that apply if a block is copied:

  • If you have a __block storage class for what would have been a stack-based C++ object, then the usual copy constructor is used.
  • If you use any other C++ stack-based object from within a block, it must have a const copy constructor. The C++ object is then copied using that constructor.

Empirically, I observe that it const copies the this pointer into the block. If the C++ instance pointed to by this is no longer at that address when the block executes (for instance, if the Worker instance on which Worker::Work() is called was stack-allocated on a higher frame), then you will get an EXC_BAD_ACCESS or worse (i.e. pointer aliasing). So it appears that:

  • It is capturing this, not copying instance variables by value.
  • Nothing is being done to keep the object pointed to by this alive.

Alternately, if I reference a locally stack-allocated (i.e. declared in this stack frame/scope) C++ object, I observe that its copy constructor gets called when it is initially captured by the block, and then again whenever the block is copied (for instance, by the operation queue when you enqueue the operation.)

To address your questions specifically:

But here, do we capture the current value of this? A copy of this using Worker’s copy constructor? Or a reference to the place where node is stored?

We capture this. Consider it a const-copy of an intptr_t if that helps.

The object fred may not exist any more when the block gets run. What is the value of node? (Assume that the underlying Node objects live forever, but Workers come and go.)

In this case, this has been captured by-value and node is effectively a pointer with the value this + <offset of node in Worker> but since the Worker instance is gone, it's effectively a garbage pointer.

I would infer no magic or other behavior other than exactly what's described in those docs.

OTHER TIPS

In C++, when you write an instance variable node, without explicitly writing something->node, it is implicitly this->node. (Similar to how in Objective-C, if you write an instance variable node, without explicitly writing something->node, it is implicitly self->node.)

So the variable which is being used is this, and it is this that is captured. (Technically this is described in the standard as a separate expression type of its own, not a variable; but for all intents and purposes it acts as an implicit local variable of type Worker *const.) As with all non-__block variables, capturing it makes a const copy of this.

Blocks have memory management semantics when they capture a variable of Objective-C object pointer type. However, this does not have Objective-C object pointer type, so nothing is done with it in terms of memory management. (There is nothing that can be done in terms of C++ memory management anyway.) So yes, the C++ object pointed to by this could be invalid by the time the block runs.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top