Учитывая несколько правок, у меня сложилось впечатление, что было бы полезно подробное резюме.
1. Когда не до
В двух ситуациях вам не следует использовать умные указатели.
Первая - это та же самая ситуация, в которой вы не должны использовать класс C++
на самом деле. IE: DLL граница, если вы не предлагаете исходный код клиенту. Скажем анекдотично.
Второе случается гораздо чаще: умный менеджер означает владение . Вы можете использовать указатели для указания на существующие ресурсы, не управляя их временем жизни, например:
void notowner(const std::string& name)
{
Class* pointer(0);
if (name == "cat")
pointer = getCat();
else if (name == "dog")
pointer = getDog();
if (pointer) doSomething(*pointer);
}
Этот пример ограничен. Но указатель семантически отличается от ссылки тем, что может указывать на недопустимое местоположение (нулевой указатель). В этом случае совершенно нормально не использовать смарт-указатель вместо него, потому что вы не хотите управлять временем жизни объекта.
2. Умные менеджеры
Если вы не пишете класс Smart Manager, если вы используете ключевое слово delete
, вы делаете что-то неправильно.
Это спорная точка зрения, но после того, как я рассмотрел множество примеров некорректного кода, я больше не рискую. Итак, если вы пишете new
, вам нужен умный менеджер для вновь выделенной памяти. И тебе это нужно прямо сейчас.
Это не значит, что вы меньше программист! Напротив, повторное использование кода, который, как было доказано, работает, вместо изобретения колеса снова и снова, является ключевым навыком.
Теперь начинается настоящая трудность: какой умный менеджер?
3. Умные указатели
Существуют различные умные указатели с различными характеристиками.
Пропуск std::auto_ptr
, который вы должны избегать (его семантическая копия прикручена).
scoped_ptr
: нет накладных расходов, не может быть скопировано или перемещено.
unique_ptr
: нет накладных расходов, не может быть скопировано, может быть перемещено.
shared_ptr
/ weak_ptr
: некоторые издержки (подсчет ссылок), могут быть скопированы.
Обычно попробуйте использовать либо scoped_ptr
, либо unique_ptr
. Если вам нужно несколько владельцев, попробуйте изменить дизайн. Если вы не можете изменить дизайн и действительно нуждаетесь в нескольких владельцах, используйте shared_ptr
, но остерегайтесь циклов ссылок, которые должны быть прерваны с помощью weak_ptr
где-то в середине.
4. Умные контейнеры
Многие интеллектуальные указатели не предназначены для копирования, поэтому их использование с контейнерами STL несколько скомпрометировано.
Вместо того, чтобы прибегать к shared_ptr
и его издержкам, используйте умные контейнеры из Boost Pointer Container . Они эмулируют интерфейс классических контейнеров STL, но хранят собственные указатели.
5. Прокатись самостоятельно
Бывают ситуации, когда вы можете захотеть накатить своего умного менеджера. Убедитесь, что вы не пропустили некоторые функции в библиотеках, которые вы используете заранее.
Написание умного менеджера при наличии исключений довольно сложно. Обычно вы не можете предположить, что память доступна (new
может дать сбой) или что Copy Constructor
s имеет гарантию no throw
.
Может быть, в некоторой степени приемлемо игнорировать исключение std::bad_alloc
и навязывать, что Copy Constructor
из ряда помощников не перестают работать ... в конце концов, именно это boost::shared_ptr
делает для своего удалителя D
параметр шаблона.
Но я бы не советовал, особенно для новичка. Это сложная проблема, и вы вряд ли заметите ошибки прямо сейчас.
6. Примеры
// For the sake of short code, avoid in real code ;)
using namespace boost;
// Example classes
// Yes, clone returns a raw pointer...
// it puts the burden on the caller as for how to wrap it
// It is to obey the `Cloneable` concept as described in
// the Boost Pointer Container library linked above
struct Cloneable
{
virtual ~Cloneable() {}
virtual Cloneable* clone() const = 0;
};
struct Derived: Cloneable
{
virtual Derived* clone() const { new Derived(*this); }
};
void scoped()
{
scoped_ptr<Cloneable> c(new Derived);
} // memory freed here
// illustration of the moved semantics
unique_ptr<Cloneable> unique()
{
return unique_ptr<Cloneable>(new Derived);
}
void shared()
{
shared_ptr<Cloneable> n1(new Derived);
weak_ptr<Cloneable> w = n1;
{
shared_ptr<Cloneable> n2 = n1; // copy
n1.reset();
assert(n1.get() == 0);
assert(n2.get() != 0);
assert(!w.expired() && w.get() != 0);
} // n2 goes out of scope, the memory is released
assert(w.expired()); // no object any longer
}
void container()
{
ptr_vector<Cloneable> vec;
vec.push_back(new Derived);
vec.push_back(new Derived);
vec.push_back(
vec.front().clone() // Interesting semantic, it is dereferenced!
);
} // when vec goes out of scope, it clears up everything ;)