我有一个正在后台线程上执行的方法。从那个方法我试图dispatch_async
主线程上的一个块。该块使用本地C ++对象,该对象应该根据Apple reference进行复制。我得到了一个分段错误,从跟踪中我看到一些非常粗略的事情正在发生。这是我的代码的简化版本。
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");
});
}
这是输出:
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()
有两件事我不明白。第一个是为什么当它到达0xbfffdfcf: A::p()
时,已经调用了该对象上的析构函数。
我正在努力解决的第二件事是为什么有这么多的拷贝构造函数被调用。我期待一个。当创建a
的副本以供块捕获时,就会发生这种情况。
我在XCC中使用Xcode 3.2.5。我在模拟器和设备上遇到了相同的行为。
答案 0 :(得分:6)
我刚在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()
正如你所看到的那样,在这种情况下,析构函数会被恰当地调用,我会在你使用的极其过时的编译器中将其归结为编译错误。
此实例中的副本似乎符合我的期望。该块在捕获时将基于堆栈的对象复制到块中。然后再次将块从堆栈复制到堆中。
答案 1 :(得分:4)
我猜多个副本是由编译器隐式复制块造成的,虽然我不明白为什么需要复制块,你认为可以引用一个实例直接将块发送到主线程。
忽略多个副本,似乎该块应该使用A的0x057ae6b4
实例,因为那是在所有副本中存活的并且在块结束后释放。对我来说听起来像编译器错误。
在任何情况下,您所做的都是非常反C ++,我建议您修改此代码,以便它具有更可预测的行为。我对你的代码的问题是你在一个代码块中使用堆栈分配的对象,这个代码块将在未来的某个未确定的时间异步执行,很久之后,拥有该堆栈分配对象的函数结束。为了支持这种事情,编译器必须在封面下生成对象的副本,但是如果查看代码,则没有迹象表明块内的a
是{{1}的副本在外面宣布。如果你需要支持这种事情,我认为你最好将你的C ++类转换为Objective-C,那么你将有一个以更可预测的方式运行的引用计数对象。
如果此对象需要保留在C ++域中,那么我建议您在堆上分配它并手动管理它的销毁,这是C ++中堆分配对象的标准。例如,你可以这样做:
a
如果这听起来太粗糙,那么您可以看到使用- (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");
});
}
还是更好,auto_ptr
效果更好。我怀疑当你使用堆栈上分配的A时,这两个会遇到相同的编译器问题,因为这些也将在堆栈上分配。但值得一试。