简体   繁体   中英

Objective-C blocks and C++ objects

I have a method that is being executed on a background thread. From that method I'm trying to dispatch_async a block on the main thread. The block uses a local C++ object which is supposed to be copy constructed according to the Apple reference . I'm getting a segmentation fault and from the trace I see that something very sketchy is going on. Here's the simplified version of my code.

struct A
{
    A() { printf("0x%08x: A::A()\n", this); }
    A(A const &that) { printf("0x%08x: A::A(A const &%p)\n", this, &that); }
    ~A() { printf("0x%08x: A::~A()\n", this); }
    void p() const { printf("0x%08x: A::p()\n", this); }
};

- (void)runs_on_a_background_thread
{
    A a;
    a.p();
    dispatch_async(dispatch_get_main_queue(), ^{
        printf("block begins\n");
        a.p();
        printf("block ends\n");
    });
}

And this is the output:

0xbfffc2af: A::A()
0xbfffc2af: A::p()
0xbfffc2a8: A::A(A const &0xbfffc2af)
0x057ae6b4: A::A(A const &0xbfffc2a8)
0xbfffc2a8: A::~A()
0xbfffc2af: A::~A()
0xbfffdfcf: A::A(A const &0x57ae6b4)
0xbfffdfcf: A::~A()
block begins
0xbfffdfcf: A::p()
block ends
0x057ae6b4: A::~A()

There are two things that I don't understand. The first one is why by the time it gets to 0xbfffdfcf: A::p() the destructor on that object has been called already.

The second thing I'm struggling with is why there are so many copy constructors being called. I expect one. That should happen when a copy of a is created to be captured by the block.

I'm using Xcode 3.2.5 with GCC. I experience the same behavior on the simulator and on the device.

I just tested this on LLVM 3.0.

0xb024ee18: A::A()
0xb024ee18: A::p()
0xb024ee04: A::A(A const &0xb024ee18)
0x06869364: A::A(A const &0xb024ee04)
0xb024ee04: A::~A()
0xb024ee18: A::~A()
block begins
0x06869364: A::p()
block ends
0x06869364: A::~A()

As you can see the destructors get called appropriately in this case, I'd chalk this up to a complier bug in the extremely outdated compiler you're using.

The copies in this instance seem inline with what I'd expect. The block copies the stack based object into the block when it gets captured. And then again when the block gets copied from the stack to the heap.

I guess that the multiple copies are caused by the block being copied implicitly a couple of times by the compiler, though I don't see why the block needs to be copied, you'd think that the one instance can be referenced directly when the block is sent to the main thread.

Ignoring the multiple copies, it seems the block should have used the 0x057ae6b4 instance of A since that is the one that survives all the copies and is freed after the block ends. Sounds like a compiler bug to me.

In any case, what you are doing is extremely anti-C++ and I recommend that you revise this code so that it has a more predictable behavior. The problem I have with your code is that you are using a stack allocated object in a block of code that will execute asynchronously at some undetermined time in the future, long after the function that owns that stack allocated object ended. To support this kind of thing the compiler has to generate a copy of your object under the covers, yet if you look at the code there is no indication that the a inside the block is a copy of the a declared outside. If you need to support this kind of thing I think you will be better off converting your C++ class to Objective-C, then you'll have a reference counted object that behaves in a more predictable way.

If this object needs to stay in the C++ domain, then I recommend that you allocate it on the heap and manage the destruction of it manually, as is standard for heap allocated objects in C++. For example, you could do something like this:

- (void)runs_on_a_background_thread
{
    A* a = new A();
    a->p();
    dispatch_async(dispatch_get_main_queue(), ^{
        printf("block begins\n");
        a->p();
        delete a;
        printf("block ends\n");
    });
}

If this sounds too raw to you, then you could see if using an auto_ptr or better yet, a shared_ptr works better. I suspect these two will have the same compiler issue seen when you work with A allocated on the stack, since these will also be allocated on the stack. But it might be worth a try.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM