"Сирий" вказівник не управляється. Тобто наступний рядок:
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;
};
Тепер, якщо ми видалимо а 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, який повинен підтримувати компілятори pre-C ++ 11, має власну модель збору сміття: у багатьох QObject
є механізм, де вони будуть належним чином знищені, не потребуючи в delete
них користувача.
Я не знаю, як QObject
буде поводитися s, коли керуються C ++ 11 керованими покажчиками, тому не можу сказати, що shared_ptr<QDialog>
це гарна ідея. У мене недостатньо досвіду роботи з Qt, щоб сказати напевно, але я вважаю, що Qt5 було скориговано для цього випадку використання.