Використання розумних покажчиків для учнів класу


159

У мене виникають проблеми з розумінням використання смарт-покажчиків як членів класу в C ++ 11. Я багато читав про розумні покажчики і, думаю, я розумію, як unique_ptrі shared_ptr/ weak_ptrв цілому працювати. Що я не розумію, це справжнє використання. Схоже, всі рекомендують використовувати unique_ptrяк шлях, який потрібно пройти майже весь час. Але як би я реалізував щось подібне:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

Скажімо, я хотів би замінити покажчики на розумні покажчики. A unique_ptrне працював би через це getDevice(), правда? Так це час, коли я використовую shared_ptrі weak_ptr? Ніякого способу використання unique_ptr? Мені здається, що для більшості випадків shared_ptrмає більше сенсу, якщо я не використовую вказівник у дійсно невеликому обсязі?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

Це шлях? Дуже дякую!


4
Це допомагає бути дійсно зрозумілим щодо терміну експлуатації, права власності та можливих нулів. Наприклад, перейшовши deviceдо конструктора settings, ви хочете все-таки мати можливість посилатися на нього в області виклику або лише через settings? Якщо остання, unique_ptrкорисна. Крім того , у вас є сценарій , в якому повертається значення getDevice()є null. Якщо ні, просто поверніть посилання.
Кіт

2
Так, shared_ptrце правильно у 8/10 випадків. Інші 2/10 розділені між unique_ptrі weak_ptr. Крім того, weak_ptrзазвичай використовується для розбиття кругових посилань; Я не впевнений, що ваше використання буде вважатися правильним.
Collin Dauphinee

2
Перш за все, яке право власності ви бажаєте для учасника deviceданих? Спочатку ви повинні вирішити це.
juanchopanza

1
Гаразд, я розумію, що в якості абонента я міг би скористатися unique_ptrзамість цього і відмовитись від права власності під час виклику конструктора, якщо я знаю, що зараз він мені більше не потрібен. Але як дизайнер Settingsкласу, я не знаю, чи хоче абонент зберегти посилання. Можливо, пристрій буде використовуватися в багатьох місцях. Гаразд, можливо, саме в цьому ваша думка. У такому випадку я не був би єдиним власником, і тоді я б використовував shared_ptr. І: настільки розумні точки замінюють покажчики, але не посилання, правда?
michaelk

це-> пристрій = пристрій; Також використовуйте списки ініціалізації.
Нілс

Відповіді:


202

A unique_ptrне працював би через це getDevice(), правда?

Ні, не обов’язково. Тут важливо визначити відповідну політику власності на ваш Deviceоб’єкт, тобто хто буде власником об'єкта, на який вказує ваш (розумний) покажчик.

Це збирається бути екземпляр Settingsоб'єкта в поодинці ? Чи Deviceповинен об'єкт знищуватися автоматично, коли Settingsоб’єкт знищується, або він повинен переживати цей об’єкт?

У першому випадку - std::unique_ptrце те, що вам потрібно, оскільки він робить Settingsєдиним (унікальним) власником загостреного об’єкта і єдиним об'єктом, який відповідає за його знищення.

Згідно з цим припущенням, getDevice()слід повернути простий спостережний покажчик (спостережні покажчики - це вказівники, які не підтримують загострений об'єкт живим). Найпростіший вид спостережувального вказівника - це необроблений покажчик:

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[ ПРИМІТКА 1: Вам може бути цікаво, чому я тут використовую необроблені покажчики, коли всі продовжують говорити, що сировинні покажчики погані, небезпечні та небезпечні. Насправді це дорогоцінне попередження, але важливо поставити його у правильному контексті: сировинні покажчики погано використовуються для виконання ручного управління пам’яттю , тобто розподілу та розміщення об’єктів через newта delete. Якщо використовується виключно як засіб для досягнення еталонної семантики та обходу невласників, спостерігаючи за вказівниками, в сирому вказівнику немає нічого власне небезпечного, за винятком, мабуть, того, що слід подбати про те, щоб не зводити звисаючий покажчик. - ЗАКРІТКА ПРИМІТКА 1 ]

[ ПРИМІТКА 2: Як з'ясувалося в коментарях, у цьому конкретному випадку, коли право власності є унікальним і об'єкт, що належить, завжди гарантовано присутній (тобто внутрішній член даних deviceніколи не буде nullptr), функція getDevice()могла б (і, можливо, повинна) повернути посилання, а не вказівник. Хоча це правда, я вирішив повернути сюди необроблений покажчик, тому що маю на увазі це короткою відповіддю, яку можна узагальнити до випадку, де це deviceможе бути nullptr, і показати, що сировинні покажчики в порядку, доки хто не використовує їх для ручне управління пам’яттю. - ЗАКРІТКА ПРИМІТКА 2 ]


Ситуація кардинально відрізняється, звичайно, якщо ваш Settingsоб'єкт не повинен мати виключне право власності на пристрій. Так може бути, наприклад, якщо знищення Settingsоб'єкта не повинно означати і знищення загостреного Deviceоб'єкта.

Це те, про що ти можеш розповісти лише ти як дизайнер своєї програми; із прикладу, який ви надаєте, мені важко сказати, так це чи ні.

Щоб допомогти вам розібратися, ви можете запитати себе, чи є інші об'єкти, окрім Settingsцього, мають право підтримувати Deviceживий об’єкт до тих пір, поки вони тримають вказівник на нього, замість того, щоб бути просто пасивними спостерігачами. Якщо це дійсно так, то вам потрібна політика спільної власності , яка std::shared_ptrпропонує:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

Зауважте, що weak_ptrце спостережний покажчик, а не володіє вказівником - іншими словами, він не підтримує вказаний об'єкт живим, якщо всі інші володіючі вказівники на вказаний об'єкт виходять за межі області.

Перевага weak_ptrнад звичайним необмеженим покажчиком полягає в тому, що ви можете сміливо сказати, чи weak_ptrвін звисає чи ні (тобто, чи вказує він на дійсний об'єкт, або якщо об'єкт, спочатку вказаний на нього, знищений). Це можна зробити, викликавши функцію- expired()член на weak_ptrоб'єкті.


4
@LKK: Так, правильно. A weak_ptr- це завжди альтернатива сировинним покажчикам спостереження. У певному сенсі це безпечніше, тому що ви можете перевірити, чи не бовтається він, перш ніж відкинути його, але він також має деякі накладні витрати. Якщо ви зможете легко гарантувати, що ви не збираєтесь зневажати звисаючий вказівник, то вам слід добре поспостерігати за сирими вказівниками
Енді Проул

6
У першому випадку, мабуть, навіть було б краще дозволити getDevice()повернути посилання, а чи не так? Тож абоненту не доведеться перевіряти nullptr.
vobject

5
@chico: Не впевнений, що ти маєш на увазі. auto myDevice = settings.getDevice()створить новий екземпляр типу, що Deviceвикликається, myDeviceі скопіює його з тієї, на яку посилається посилання, що getDevice()повертається. Якщо ви хочете myDeviceбути довідником, вам потрібно це зробити auto& myDevice = settings.getDevice(). Тож якщо я чогось не пропускаю, ми знову опинимось у тій же ситуації, що і у нас, не використовуючи auto.
Енді Проул

2
@Prrformance: Оскільки ви не хочете віддавати право власності на об'єкт - передача unique_ptrклієнта модифікованого відкриває можливість, що клієнт перейде від нього, тим самим набуваючи право власності та залишаючи вам нульовий (унікальний) покажчик.
Енді Проул

7
@Prrformance: Хоча це не дозволить клієнту рухатися (якщо тільки клієнт не є шаленим вченим, який захоплюється const_casts), я особисто не став би цього робити. Він розкриває деталі реалізації, тобто той факт, що право власності є унікальним і реалізується через unique_ptr. Я бачу все так: якщо ви хочете / вам потрібно передати / повернути право власності, передайте / поверніть розумний вказівник ( unique_ptrабо shared_ptr, залежно від виду власності). Якщо ви не хочете / не повинні передавати / повертати право власності, використовуйте (правильно- constкваліфікований) покажчик або посилання, переважно залежно від того, чи може аргумент бути нульовим чи ні.
Енді Проул

0
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptrвикористовується тільки для опорних петель. Графік залежності повинен бути ациклічнонаправленим графіком. У загальних покажчиках є 2 опорні підрахунки: 1 для shared_ptrs та 1 для всіх покажчиків ( shared_ptrі weak_ptr). Коли всі shared_ptrs видалено, вказівник видаляється. Коли вказівник потрібен від weak_ptr, його lockслід використовувати для отримання вказівника, якщо він існує.


Отже, якщо я правильно зрозумів вашу відповідь, розумні вказівники замінюють необроблені вказівники, але це не обов'язково посилання?
michaelk

Чи є насправді два відліки в а shared_ptr? Чи можете ви поясніть, чому? Наскільки я розумію, weak_ptrце не потрібно рахувати, оскільки він просто створює новий shared_ptrпри роботі над об'єктом (якщо базовий об'єкт все ще існує).
Бьорн Поллекс

@ BjörnPollex: Я створив для вас короткий приклад: посилання . Я не реалізував все, лише конструктори копій і lock. версія boost також безпечна для підрахунку посилань ( deleteвикликається лише один раз).
Naszta

@Naszta: Ваш приклад показує , що можна здійснити це з допомогою двох лічильників посилань, але ваша відповідь говорить про те , що це потрібно , що я не вважаю , що це. Чи можете ви уточнити це у своїй відповіді?
Björn Pollex

1
@ BjörnPollex, weak_ptr::lock()щоб сказати, чи закінчився термін дії об’єкта, він повинен перевірити "блок управління", який містить перший номер відліку та вказівник на об'єкт, тому блок управління не повинен бути зруйнований, поки weak_ptrоб’єкти ще використовуються, тому кількість weak_ptrоб'єктів повинна бути відстежена, що і робить другий відлік. Об'єкт руйнується, коли перше число посилань падає до нуля, блок керування руйнується, коли другий відлік пониження падає до нуля.
Jonathan Wakely
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.