Использование интеллектуальных указателей для членов класса

У меня возникли проблемы с пониманием использования интеллектуальных указателей как членов класса в С++ 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...
}

Это путь? Большое спасибо!

Ответ 1

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: Возможно, вам интересно, почему я использую raw-указатели здесь, когда все говорят, что raw-указатели плохие, небезопасные и опасные. На самом деле это ценное предупреждение, но важно поставить его в правильном контексте: raw-указатели плохие при использовании для управления ручным управлением памятью, то есть распределение и освобождение объектов через 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() member в объекте weak_ptr.

Ответ 2

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_ptr s и 1 для всех указателей (shared_ptr и weak_ptr). Когда все shared_ptr удаляются, указатель удаляется. Когда требуется указатель от weak_ptr, lock следует использовать для получения указателя, если он существует.