Какими способами вы можете стрелять в ногу при использовании boost::shared_ptr
? Другими словами, какие недостатки я должен избегать, когда я использую boost::shared_ptr
?
Каковы потенциальные опасности при использовании boost:: shared_ptr?
Ответ 1
Циклические ссылки: 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 работают таким образом).
Проблема заключается в том, что если вы потеряете ссылку на самый верхний node, то самый верхний node по-прежнему содержит сильные ссылки на своих детей, и все его дети также имеют сильную ссылку на своих родителей. Это означает, что есть циркулярные ссылки, позволяющие всем экземплярам очищать себя, в то время как нет никакого способа фактически достичь дерева из кода, эта память течет.
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_;
};
Здесь родительский node был заменен слабым указателем. Он больше не имеет права голоса в течение срока службы node, к которому он относится. Таким образом, если верхний node выходит за рамки, как в предыдущем примере, тогда, когда он содержит сильные ссылки на своих детей, его дети не содержат сильных ссылок на своих родителей. Таким образом, нет никаких сильных ссылок на объект, и он очищается. В свою очередь это приводит к тому, что дети теряют свою сильную ссылку, которая заставляет их очищаться и т.д. Короче говоря, это обыкновение течь. И просто стратегически заменив shared_ptr < > на weak_ptr < > .
Примечание. Вышеупомянутое относится в равной степени к std:: shared_ptr < > и std:: weak_ptr < > , как это делается для boost:: shared_ptr < > и boost:: weak_ptr < > .
Ответ 2
Создание нескольких несвязанных 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;
}
Ответ 3
Построение анонимного временного общего указателя, например внутри аргументов вызова функции:
f(shared_ptr<Foo>(new Foo()), g());
Это потому, что допустимо выполнение new Foo()
, а затем g()
и g()
для исключения, без установки shared_ptr
, поэтому shared_ptr
не выполняет есть возможность очистить объект Foo
.
Ответ 4
Будьте осторожны, делая два указателя на один и тот же объект.
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. Просто убедитесь, что он скопирован до его сохранения (т.е. Нет ссылок как членов класса).
Ответ 5
Вот две вещи, которые следует избегать:
-
Вызов функции
get()
для получения необработанного указателя и использования его после того, как объект с заостренным объектом выходит из области видимости. -
Передача ссылки или необработанного указателя на
shared_ptr
также должна быть опасной, поскольку она не будет увеличивать внутренний счет, который помогает сохранить объект в живых.
Ответ 6
Мы отлаживаем несколько недель странного поведения.
Причина:
мы передали 'this' некоторым работникам потоков вместо "shared_from_this".
Ответ 7
Не точно нога, но, безусловно, источник разочарования, пока вы не обернете вокруг себя, как это сделать С++ 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 затрагивается предмет с большим количеством примеров кода. Я также blogged об этом несколько лет назад, когда мне сначала нужно было выяснить, как это сделать.
Ответ 8
Использование 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 миллионов байт вашей памяти, удерживая только count = 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);
}
Ответ 9
Выделение shared_ptr <T> к этому внутри определения класса также опасно. Вместо этого используйте enabled_shared_from_this.
См. следующий пост здесь
Ответ 10
Вам нужно быть осторожным, если вы используете shared_ptr
в многопоточном коде. Тогда довольно легко стать в случае, когда пара shared_ptr
s, указывающая на одну и ту же память, используется разными потоками.
Ответ 11
Популярное широкое использование shared_ptr почти неизбежно приведет к нежелательной и невидимой памяти.
Циклические ссылки - хорошо известная причина, и некоторые из них могут быть косвенными и трудными для обнаружения особенно в сложном коде, который обрабатывается более чем одним программистом; программист может решить, что один объект нуждается в ссылке на другую в качестве быстрого исправления и не успевает изучить весь код, чтобы увидеть, закрывает ли он цикл. Эта опасность сильно недооценивается.
Менее понятна проблема неизданных ссылок. Если объект разделяется на многие shared_ptrs, он не будет уничтожен, пока каждый из них не обнуляется или не выходит за рамки. Очень легко упускать из виду одну из этих ссылок и в конечном итоге скрывать невидимые в памяти объекты, о которых вы думали, что закончили с ними.
Хотя строго говоря, это не утечка памяти (все это будет выпущено до выхода программы), они настолько же вредны и сложнее обнаружить.
Эти проблемы являются следствием целесообразных ложных объявлений: 1. Объявление того, что вы действительно хотите быть единоличным, как shared_ptr. scoped_ptr будет правильным, но тогда любая другая ссылка на этот объект должна быть необработанным указателем, который может быть оставлен болтающимся. 2. Объявление того, что вы действительно хотите быть пассивной ссылкой наблюдения как shared_ptr. weak_ptr будет правильным, но тогда у вас возникнут проблемы с преобразованием его в share_ptr каждый раз, когда вы хотите его использовать.
Я подозреваю, что ваш проект - прекрасный пример той проблемы, с которой эта практика может вас заинтересовать.
Если у вас есть приложение с интенсивной памятью, вам действительно нужно единое владение, чтобы ваш проект мог явно контролировать время жизни объекта.
При одиночном праве собственности opObject = NULL; обязательно удалит объект, и он сделает это сейчас.
При совместном владении spObject = NULL;........ кто знает?......
Ответ 12
Если у вас есть реестр общих объектов (например, список всех активных экземпляров), объекты никогда не будут освобождены. Решение: как и в случае структур круговой зависимости (см. Ответ Каз Дракона), используйте weak_ptr, если это необходимо.
Ответ 13
Умные указатели не для всех, и необработанные указатели не могут быть устранены
Вероятно, самая страшная опасность состоит в том, что, поскольку shared_ptr
- полезный инструмент, люди начнут все это ставить. Поскольку простые указатели могут быть неправильно использованы, одни и те же люди будут искать исходные указатели и пытаться заменить их на строки, контейнеры или интеллектуальные указатели, даже если это не имеет смысла. Легитимное использование исходных указателей станет подозрительным. Будет указатель полиции.
Это не только самая страшная опасность, это может быть единственная серьезная опасность. Все худшие злоупотребления shared_ptr
будут прямым следствием идеи о том, что интеллектуальные указатели превосходят исходный указатель (что бы это ни значило), и что размещение интеллектуальных указателей везде сделает программирование на С++ "более безопасным".
Конечно, тот факт, что умный указатель нужно преобразовать в необработанный указатель, который будет использоваться, опровергает это утверждение культа умных курсоров, но тот факт, что доступ к необработанному указателю "неявный" в operator*
, operator->
(или явно в get()
), но не подразумевается в неявном преобразовании, достаточно, чтобы создать впечатление, что это не действительно преобразование, и что необработанный указатель, созданный этим не-преобразованием, является безвредным временным.
С++ не может быть "безопасным языком", и никакое полезное подмножество С++ не является "безопасным"
Конечно, стремление к безопасному подмножеству ( "безопасному" в строгом смысле "безопасности памяти", как LISP, Haskell, Java...) С++ обречено быть бесконечным и неудовлетворительным, так как безопасный Подмножество С++ является крошечным и почти бесполезным, поскольку небезопасные примитивы являются правилом, а не исключением. Строгая безопасность памяти в С++ означает отсутствие указателей и только ссылки с автоматическим классом хранения. Но на языке, где программисту доверяют определение, некоторые люди будут настаивать на использовании некоторого (в принципе) идиот-защищенного "умного указателя", даже если нет другого преимущества перед грубыми указателями, что один исключается конкретный способ завинчивания состояния программы.