Разумно ли использовать shared_ptr вместо unique_ptr в качестве члена класса, чтобы избежать неявного удаления конструктора копирования? - PullRequest
0 голосов
/ 19 февраля 2019

Я хотел бы сохранить конструктор копирования по умолчанию для класса с большим числом элементов (но, скажем, не особо сложного *), но в идеале хотел бы заменить некоторый элемент необработанного указателя альтернативой интеллектуального указателя.

unique_ptr, кажется, по умолчанию для этого, но он неявно удаляет конструктор копирования для моего класса.

shared_ptr вместо этого позволит мне сохранить конструктор копирования класса.Может ли это быть хорошей причиной просто придерживаться shared_ptr, даже если я действительно не хочу «делиться» ресурсом;Я действительно хочу сохранить только доступный конструктор копирования (раздражает написание ручного конструктора копирования для всего класса, просто заменить указатель на unique_ptr), так же, как это было у меня, когда я все еще использовал необработанный указатель.

В поисках того, когда использовать shared_ptr против unique_ptr, я никогда не вижу простого сохранения конструктора копирования, указанного в качестве возможной ключевой причины для использования shared_ptr (возможное исключение https://stackoverflow.com/a/16030118/3673329, но без указания каких-либо подробностей), но ятакже напрямую не вижу причин, по которым это не может быть правильным выбором.

Я считаю, что shared_ptr может быть немного более ресурсоемким, но, допустим, мой случай, когда это не является реальной проблемой.

* В частности, копирование класса по умолчанию / мелкое копирование было вполне подходящим для моих целей, пока я использовал неструктурированные члены-указатели вместо умных.

Ответы [ 2 ]

0 голосов
/ 19 февраля 2019

Все еще есть большое преимущество в использовании unique_ptr и предоставлении вручную пропущенных операций копирования.

Вы получаете гарантии правильности плюс простую точку настройки для клонирования внутренних объектов.

пример:

#include <memory>


struct LargeImplObject
{
    int x[1000];
};

struct CopyableHandle
{
    using impl_class = LargeImplObject;
    using implementation_type = std::unique_ptr<impl_class>;

    CopyableHandle()
    : impl_(construct())
    {}

    CopyableHandle(CopyableHandle&&) noexcept = default;
    CopyableHandle& operator=(CopyableHandle&&) noexcept = default;

    CopyableHandle(CopyableHandle const& other)
    : impl_(other.clone())
    {}

    CopyableHandle& operator=(CopyableHandle const& other)
    {
        impl_ = other.clone();
        return *this;
    }

    // note: no destructor

private:

    // customisation points
    static auto construct() -> implementation_type
    {
        return std::make_unique<impl_class>();
    }

    auto clone() const -> implementation_type
    {
        return std::make_unique<impl_class>(*impl_);
    }


    implementation_type impl_;
};

int main()
{
    auto a = CopyableHandle();
    auto b = a;
    auto c = std::move(a);
    a = std::move(c);
}
0 голосов
/ 19 февраля 2019

Если единственная причина для использования std::shared_ptr заключается в том, чтобы сохранить конструктивность копирования вашего класса по умолчанию, вы отдаете предпочтение простоте использования над предполагаемой семантикой вашего класса в отношении обработки ресурсов.По моему мнению, вы должны решить заранее, должен ли класс делиться своими ресурсами или владеть им исключительно.Если вы не уверены, должен ли класс делиться своими ресурсами со скопированными экземплярами или нет, что-то еще в дизайне может, по крайней мере, быть подозрительным.

Возможно, есть исключение из этого руководства, а это std::shared_ptr<const YourType>.В случае доступа только для чтения, может быть приемлемо использовать такую ​​технику, чтобы разрешить создание копии по умолчанию (хотя в другом контексте здесь - это то, где Шон Родитель говорит, что std::shared_ptr<const T> приемлемо для получениясемантика значения).

Обратите внимание на еще одно следствие: если вы совместно используете экземпляр std::shared_ptr, вы не только делитесь состоянием и функциональностью пуантиста, но и совместно управляете временем жизни.Если последнее не то, что вы намереваетесь, просто поделитесь ссылкой (предпочтительно) или необработанным указателем на указателя, с доступом, например, через функцию-член, подобную получателю.Если потребляющие части вашего класса не могут знать, жив ли пуанти или он уже уничтожен, вариант std::weak_ptr может быть вариантом.

...