«Сырой» указатель неуправляемый. То есть следующая строка:
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<>объектов, которые ссылаются на один и тот же указатель. Когда создается новый, количество увеличивается. Когда кто-то уничтожен, счет уменьшается. Когда счетчик достигает нуля, указатель равен deleted.
Таким образом, возникает проблема: структуры с двойными связями заканчиваются циклическими ссылками. Скажем, мы хотим добавить 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, есть циклическая ссылка на него. Это никогда не будет deleted, потому что его счетчик ссылок никогда не будет нулевым.
Чтобы решить эту проблему, вы используете 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 был настроен для этого варианта использования.