Как эффективно использовать intrusive_ptr Boost? - PullRequest
11 голосов
/ 25 марта 2012

Ручной 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)

Справочно-на-указатель

После пропирания исходного кода 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;
    }
}

Интуитивно понятно, что этот параметр указателя на указатель должен присвоить значение l p в контексте вызова 0. Bjarne также упоминает эту идиому . Однако в отмеченной строке утверждение все еще не выполнено, и на этот раз я ничего не понимаю.

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

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

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() избегает ссылки при возврате указателя из C API, что компенсирует ручную ссылку в f().

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

Ответы [ 2 ]

13 голосов
/ 26 марта 2012

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

2 голосов
/ 25 марта 2012

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

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

...