Как эффективно использовать Boost intrusive_ptr?

Manual Unref

У меня проблема с интрузивным указателем Boost. Он логический оператор преобразования проверяет x.get() != 0. Тем не менее, код ниже не работает в отмеченной точке. Почему это так?

Я предполагаю, что мне может быть связано с тем, что delete не устанавливает указатель на 0 (или nullptr). Если это не так, как я могу эффективно использовать интрузивный указатель? Я хотел бы иметь возможность использовать интрузивный указатель, как обычный указатель, например, в выражении x && x->foo(), но этот артефакт, по-видимому, исключает его.

#include <atomic>
#include <boost/intrusive_ptr.hpp>

struct T
{
    T() : count(0u) { }

    size_t ref_count()
    {
        return count;
    }

    std::atomic_size_t count;
};

void intrusive_ptr_add_ref(T* p)
{
    ++p->count;
}

void intrusive_ptr_release(T* p)
{
    if (--p->count == 0u)
        delete p;
}

int main()
{
    boost::intrusive_ptr<T> x;
    x = new T;
    assert(x->ref_count() == 1);

    auto raw = x.get();
    intrusive_ptr_add_ref(raw);
    intrusive_ptr_add_ref(raw);
    assert(x->ref_count() == 3);

    intrusive_ptr_release(raw);
    intrusive_ptr_release(raw);
    assert(x->ref_count() == 1);

    intrusive_ptr_release(raw); // Destroys T, ref_count() == 0.
    assert(! x); // Fails.

    return 0;
}

(Архитектура: Darwin 10.7, проверенные компиляторы g++ 4.7 и 4.6 с -std=c++11)

Референс-к-Pointer

После прополки исходного кода intrusive_ptr<T> я обнаружил, что в деструкторе есть только один вызов intrusive_ptr_release:

~intrusive_ptr()
{
    if( px != 0 ) intrusive_ptr_release( px );
}

Поскольку аргумент px типа T* является lvalue, его можно установить равным нулю, слегка изменив сигнатуру функции intrusive_ptr_release:

inline void intrusive_ptr_release(T*& p)
{
    if (--p->count == 0u)
    {
        delete p;
        p = 0;
    }
}

Интуитивно, этот указатель reference-to-pointer parameter должен присваивать lvalue p в вызывающем контексте равным 0. Bjarne также упоминает эту идиому. Тем не менее, утверждение все еще терпит неудачу на отмеченной строке, оставив меня невежественным на этот раз.

Пример использования

Причина, по которой я исправляю и не исправляю указатель вручную, заключается в том, что мне нужно некоторое время работать с необработанным указателем при передаче его в C API. Это означает, что я должен отбросить его, прежде чем передавать его в API C, чтобы предотвратить разрушение, и воссоздать интрузивный указатель из исходного указателя, когда я его верну. Вот пример:

void f()
{
    intrusive_ptr<T> x = new T;
    auto raw = x.get();
    intrusive_ptr_add_ref(raw);
    api_in(raw);
}

void g()
{
    T* raw = api_out();
    intrusive_ptr<T> y(raw, false);
    h(y);
}

Здесь второй параметр в конструкции y в g() избегает ссылки ref при возврате указателя из C API, который компенсирует ручной ref в f().

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

Ответ 1

Вопрос: почему вы ожидаете, что x будет преобразован в false в конце? Вы беспорядочно возитесь с рефректором! Вы уменьшаете его до нуля, хотя есть еще intrusive_ptr - x -, который указывает на объект. Это не так, как это работает. Счетчик ref должен быть как минимум равным числу объектов intrusive_ptr, которые указывают на подсчитанный объект - иначе он не будет счетчиком ref, не так ли?

Ответ 2

Чтение документации на intrusive_ptr Я вижу, что нет связи между "уничтожением" объекта, использованием его собственной терминологии, а указателем является 0. Итак, если вы хотите использовать идиому x && x->foo(), ваш intrusive_ptr_release должна также установить указатель на 0.

Я вижу проектное решение здесь в intrusive_ptr. Когда вызов intrusive_ptr_release вызывается, должно выполняться только уничтожение без каких-либо других действий, кроме того, что предоставляется delete, поэтому, если вы также хотите поместить указатель на 0 для поддержки идиомы, вы должны сделать это в ваш код для этой функции, но сам intrusive_ptr не заставляет вас включать больше ограничений, чем сам delete, то есть он не принуждает вас к reset указателю на 0.