Objective-C块和c++对象

Objective-C blocks and C++ objects

本文关键字:对象 c++ 块和 Objective-C      更新时间:2023-10-16

我有一个正在后台线程上执行的方法。从这个方法中,我试图在主线程上dispatch_async一个块。该块使用一个局部c++对象,该对象应该是根据Apple引用复制构造的。我得到了一个分割错误,从跟踪中我看到一些非常粗略的事情正在发生。这是我的代码的简化版本。

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 beginsn");
        a.p();
        printf("block endsn");
    });
}

输出:

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的副本以被块捕获时。

我使用Xcode 3.2.5与GCC。我在模拟器和设备上都遇到了相同的行为

我刚刚在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()

可以看到,在这种情况下,析构函数得到了适当的调用,我认为这是您使用的非常过时的编译器中的编译器错误。

本例中的副本似乎与我所期望的一致。当对象被捕获时,块将基于堆栈的对象复制到块中。然后,当块从堆栈复制到堆时,

我猜多个副本是由编译器隐式复制块几次引起的,虽然我不明白为什么需要复制块,你会认为一个实例可以在块发送到主线程时直接引用。

忽略多个副本,似乎块应该使用A的0x057ae6b4实例,因为它在所有副本中幸存下来,并在块结束后被释放。听起来像是编译器的bug。

无论如何,您所做的都是极端反c++的,我建议您修改这段代码,使其具有更可预测的行为。我对你的代码的问题是,你在一个代码块中使用堆栈分配对象,该代码块将在未来的一些不确定的时间异步执行,在拥有该堆栈分配对象的函数结束后很久。为了支持这类事情,编译器必须在后台生成对象的副本,但是如果你看一下代码,没有迹象表明块内的a是外部声明的a的副本。如果你需要支持这种东西,我认为你最好把你的c++类转换成Objective-C,这样你就会有一个引用计数对象,它的行为方式更可预测。

如果这个对象需要留在c++域中,那么我建议您在堆上分配它并手动管理它的销毁,这是c++中堆分配对象的标准。例如,您可以这样做:

- (void)runs_on_a_background_thread
{
    A* a = new A();
    a->p();
    dispatch_async(dispatch_get_main_queue(), ^{
        printf("block beginsn");
        a->p();
        delete a;
        printf("block endsn");
    });
}

如果这对您来说听起来太原始,那么您可以看看是否使用auto_ptr或更好的shared_ptr更好。我怀疑这两个将有相同的编译器问题,当您使用堆栈上分配的A时,因为它们也将在堆栈上分配。但也许值得一试。