Reputation: 2080
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?
Upvotes: 10
Views: 4011
Reputation: 29926
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:
this
, not copying instance variables by value.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 ofnode
? (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.
Upvotes: 11
Reputation: 122489
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.
Upvotes: 1