«Сырой» указатель неуправляемый. То есть следующая строка:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... утечка памяти, если сопровождающее delete
не выполнено в надлежащее время.
auto_ptr
Чтобы минимизировать эти случаи, std::auto_ptr<>
был введен. Однако из-за ограничений C ++ до стандарта 2011 года auto_ptr
утечка памяти все еще очень проста . Это достаточно для ограниченных случаев, таких как это, однако:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
Один из его самых слабых вариантов использования находится в контейнерах. Это связано с тем, что если auto_ptr<>
сделана копия объекта, а старая копия не была тщательно сброшена, то контейнер может удалить указатель и потерять данные.
unique_ptr
В качестве замены C ++ 11 представил std::unique_ptr<>
:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
Такое unique_ptr<>
будет правильно очищено, даже если оно передается между функциями. Это достигается семантическим представлением «владения» указателя - «владелец» очищает его. Это делает его идеальным для использования в контейнерах:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
В отличие от этого auto_ptr<>
, unique_ptr<>
здесь хорошо себя ведут, и при vector
изменении размеров ни один из объектов не будет случайно удален, пока vector
копирует свое резервное хранилище.
shared_ptr
а также weak_ptr
unique_ptr<>
Конечно, это полезно, но есть случаи, когда вы хотите, чтобы две части вашей кодовой базы могли ссылаться на один и тот же объект и копировать указатель, при этом гарантируя надлежащую очистку. Например, дерево может выглядеть так при использовании std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
В этом случае мы можем даже удерживать несколько копий корневого узла, и дерево будет должным образом очищено после уничтожения всех копий корневого узла.
Это работает, потому что каждый shared_ptr<>
держит не только указатель на объект, но также и счетчик ссылок всех shared_ptr<>
объектов, которые ссылаются на один и тот же указатель. Когда создается новый, количество увеличивается. Когда кто-то уничтожен, счет уменьшается. Когда счетчик достигает нуля, указатель равен delete
d.
Таким образом, возникает проблема: структуры с двойными связями заканчиваются циклическими ссылками. Скажем, мы хотим добавить parent
указатель на наше дерево Node
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Теперь, если мы удалим a Node
, есть циклическая ссылка на него. Это никогда не будет delete
d, потому что его счетчик ссылок никогда не будет нулевым.
Чтобы решить эту проблему, вы используете std::weak_ptr<>
:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Теперь все будет работать правильно, и удаление узла не оставит застрявших ссылок на родительский узел. Это делает прогулку по дереву немного более сложной, однако:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
Таким образом, вы можете заблокировать ссылку на узел, и у вас есть разумная гарантия, что он не исчезнет, пока вы работаете над ним, поскольку вы держитесь shared_ptr<>
за него.
make_shared
а также make_unique
Теперь, есть некоторые незначительные проблемы с shared_ptr<>
и unique_ptr<>
которые должны быть решены. Следующие две строки имеют проблему:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Если thrower()
выдает исключение, обе строки будут пропускать память. Более того, shared_ptr<>
счетчик ссылок находится далеко от объекта, на который он указывает, и это может означать повторное распределение). Это обычно не желательно.
C ++ 11 предоставляет std::make_shared<>()
и C ++ 14 предоставляет std::make_unique<>()
решить эту проблему:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
Теперь в обоих случаях, даже если thrower()
выдает исключение, не будет утечки памяти. В качестве бонуса, make_shared<>()
имеет возможность создать счетчик ссылок в том же пространстве памяти, что и управляемый объект, который может быть быстрее и сэкономить несколько байтов памяти, обеспечивая при этом исключительную гарантию безопасности!
Заметки о Qt
Следует отметить, однако, что Qt, который должен поддерживать компиляторы до C ++ 11, имеет свою собственную модель сборки мусора: у многих QObject
есть механизм, где они будут уничтожены должным образом без необходимости пользователя для delete
них.
Я не знаю, как QObject
будет вести себя s при управлении с помощью управляемых указателей C ++ 11, поэтому не могу сказать, что shared_ptr<QDialog>
это хорошая идея. У меня недостаточно опыта работы с Qt, чтобы сказать наверняка, но я считаю, что Qt5 был настроен для этого варианта использования.