Подтвердить что ты не робот

Objective-C блоки и объекты С++

У меня есть метод, который выполняется в фоновом потоке. Из этого метода я пытаюсь dispatch_async блокировать основной поток. Блок использует локальный объект С++, который должен быть скопирован в соответствии с ссылкой 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 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, которая будет захвачена блоком.

Я использую Xcode 3.2.5 с GCC. Я испытываю такое же поведение на симуляторе и на устройстве.

4b9b3361

Ответ 1

Я только что протестировал это на 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()

Как вы можете видеть, деструкторы получаются соответствующим образом вызваны в этом случае, я бы помешал это до ошибки, связанной с компилятором в крайне устаревшем компиляторе, который вы используете.

Копии в этом экземпляре кажутся встроенными с тем, что я ожидаю. Блок копирует объект на основе стека в блок, когда он захватывается. И снова, когда блок будет скопирован из стека в кучу.

Ответ 2

Я предполагаю, что множественные копии вызваны тем, что блок копируется неявно пару раз компилятором, хотя я не понимаю, почему блок нужно скопировать, можно подумать, что на один экземпляр можно ссылаться непосредственно, когда блок отправляется в основной поток.

Игнорируя несколько копий, кажется, что блок должен был использовать экземпляр 0x057ae6b4 для A, поскольку это тот, который выживает во всех копиях и освобождается после завершения блока. Звучит как ошибка компилятора для меня.

В любом случае то, что вы делаете, чрезвычайно анти-С++, и я рекомендую вам пересмотреть этот код, чтобы он имел более предсказуемое поведение. Проблема с вашим кодом заключается в том, что вы используете выделенный стек стек в блоке кода, который будет выполняться асинхронно в какое-то неопределенное время в будущем, спустя долгое время после того, как функция, которой принадлежит этот выделенный объект, закончила стек. Для поддержки такого рода компилятор должен сгенерировать копию вашего объекта под обложками, но если вы посмотрите на код, нет никаких указаний на то, что a внутри блока является копией объявленного a. Если вам нужно поддержать подобное, я думаю, вам будет лучше конвертировать ваш класс С++ в Objective-C, тогда у вас будет объект подсчитанных ссылок, который ведет себя более предсказуемым образом.

Если этот объект должен оставаться в С++-домене, я рекомендую вам выделить его в кучу и управлять его уничтожением вручную, как это стандартно для кучи выделенных объектов в С++. Например, вы можете сделать что-то вроде этого:

- (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 или еще лучше, а shared_ptr работает лучше. Я подозреваю, что у этих двух будет такая же проблема с компилятором, когда вы работаете с A, выделенным в стеке, поскольку они также будут выделены в стеке. Но, возможно, стоит попробовать.