Каковы потенциальные опасности при использовании boost :: shared_ptr? - PullRequest
48 голосов
/ 31 марта 2009

Какими способами вы можете выстрелить себе в ногу при использовании boost::shared_ptr? Другими словами, каких ловушек я должен избегать, когда использую boost::shared_ptr?

Ответы [ 13 ]

42 голосов
/ 31 марта 2009

Циклические ссылки: a shared_ptr<> на то, что имеет shared_ptr<> на исходный объект. Конечно, вы можете использовать weak_ptr<>, чтобы разорвать этот цикл.


Я добавляю следующее в качестве примера того, о чем я говорю в комментариях.

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        if (parent_) parent_->frob();
    }

private :
    void do_frob();
    shared_ptr<node> parent_;
    vector< shared_ptr<node> > children_;
};

В этом примере у вас есть дерево узлов, каждый из которых содержит указатель на своего родителя. Функция-член frob () по какой-либо причине ряби вверх по дереву. (Это не совсем странно; некоторые GUI-фреймворки работают таким образом).

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

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
        if (parent) parent->frob();
    }

private :
    void do_frob();
    weak_ptr<node> parent_; // Note: now a weak_ptr<>
    vector< shared_ptr<node> > children_;
};

Здесь родительский узел был заменен слабым указателем. У него больше нет права голоса во время жизни узла, к которому он относится. Таким образом, если самый верхний узел выходит из области видимости, как в предыдущем примере, тогда, хотя он содержит строгие ссылки на своих дочерних элементов, его дочерние элементы не содержат строгих ссылок на своих родителей. Таким образом, нет сильных ссылок на объект, и он очищается. В свою очередь, это приводит к тому, что дети теряют одно сильное упоминание, которое заставляет их убирать и так далее. Короче говоря, это не утечка. И просто путем стратегической замены shared_ptr <> на weak_ptr <>.

Примечание. Вышеприведенное относится в равной степени к std :: shared_ptr <> и std :: weak_ptr <>, как и для boost :: shared_ptr <> и boost :: weak_ptr <>.

24 голосов
/ 31 марта 2009

Создание нескольких несвязанных shared_ptr для одного и того же объекта:

#include <stdio.h>
#include "boost/shared_ptr.hpp"

class foo
{
public:
    foo() { printf( "foo()\n"); }

    ~foo() { printf( "~foo()\n"); }
};

typedef boost::shared_ptr<foo> pFoo_t;

void doSomething( pFoo_t p)
{
    printf( "doing something...\n");
}

void doSomethingElse( pFoo_t p)
{
    printf( "doing something else...\n");
}

int main() {
    foo* pFoo = new foo;

    doSomething( pFoo_t( pFoo));
    doSomethingElse( pFoo_t( pFoo));

    return 0;
}
18 голосов
/ 04 апреля 2009

Создание анонимного временного общего указателя, например, внутри аргументов вызова функции:

f(shared_ptr<Foo>(new Foo()), g());

Это потому, что допустимо выполнение new Foo(), затем вызывается g() и g() для выдачи исключения без установки shared_ptr, поэтому shared_ptr не вызывает есть шанс убрать объект Foo.

13 голосов
/ 04 апреля 2009

Будьте осторожны, делая два указателя на один и тот же объект.

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer

b->doSomething(); // crashes

вместо этого используйте

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d = 
    boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--

b->doSomething(); // no crash

Кроме того, любые классы, содержащие shared_ptrs, должны определять конструкторы копирования и операторы присваивания.

Не пытайтесь использовать shared_from_this () в конструкторе - это не сработает. Вместо этого создайте статический метод для создания класса и верните ему shared_ptr.

Я передал ссылки на shared_ptrs без проблем. Просто убедитесь, что он скопирован перед сохранением (т. Е. Нет ссылок в качестве членов класса).

12 голосов
/ 31 марта 2009

Вот две вещи, которых следует избегать:

  • Вызов функции get() для получения необработанного указателя и использования его после выхода объекта, на который указывает указатель.

  • Передача ссылки или необработанного указателя на shared_ptr также должна быть опасной, поскольку она не увеличивает внутренний счетчик, что помогает поддерживать объект в живых.

9 голосов
/ 31 марта 2009

Отлаживаем несколько недель странное поведение.

Причина была:
мы передали 'this' некоторым потоковым работникам вместо 'shared_from_this'.

4 голосов
/ 07 августа 2009

Не совсем пулемет, но, безусловно, источник разочарования, пока вы не обдумаете, как это сделать C ++ 0x: большинство предикатов, которые вы знаете и любите из <functional>, не очень хорошо играют с shared_ptr. К счастью, std::tr1::mem_fn работает с объектами, указателями и shared_ptr s, заменяя std::mem_fun, но если вы хотите использовать std::negate, std::not1, std::plus или любого из этих старых друзей с shared_ptr, будьте готов к тому, чтобы привыкнуть к std::tr1::bind и, возможно, к заполнителям аргументов. На практике это на самом деле намного более обобщенно, так как теперь вы в основном используете bind для каждого адаптера объекта функции, но это требует некоторого привыкания, если вы уже знакомы с вспомогательными функциями STL.

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

3 голосов
/ 23 марта 2010

Использование shared_ptr для действительно небольших объектов (например, char short) может быть непосильным, если у вас в куче много маленьких объектов, но они не являются "общими". boost::shared_ptr выделяет 16 байтов для каждого нового счетчика ссылок, который он создает на g ++ 4.4.3 и VS2008 с Boost 1.42. std::tr1::shared_ptr выделяет 20 байтов. Теперь, если у вас есть миллион различных shared_ptr<char>, это означает, что 20 миллионов байт вашей памяти ушли на хранение, просто считать = 1. Не говоря уже о косвенных расходах и фрагментации памяти. Попробуйте со следующим на вашей любимой платформе.

void * operator new (size_t size) {
  std::cout << "size = " << size << std::endl;
  void *ptr = malloc(size);
  if(!ptr) throw std::bad_alloc();
  return ptr;
}
void operator delete (void *p) {
  free(p);
}
1 голос
/ 04 апреля 2009

Вы должны быть осторожны, когда используете shared_ptr в многопоточном коде. Тогда сравнительно легко оказаться в случае, когда пара shared_ptr s, указывающая на одну и ту же память, используется разными потоками.

1 голос
/ 03 апреля 2009

Предоставление shared_ptr для этого внутри определения класса также опасно. Вместо этого используйте enabled_shared_from_this.

См. Следующий пост здесь

...