2

I would like to preserve the default copy-constructor of a large-ish (but say not particularly complex*) class, but ideally would like to replace some raw pointer member with a smart-pointer alternative.

unique_ptr seems to be the default for this, but it implicitly deletes the copy constructor for my class.

shared_ptr instead would allow me to preserve the class' copy constructor. Could that likely be a good reason to simply stick to shared_ptr, even if I do not genuinely want to 'share' the resource; I really only want to preserve the readily available copy constructor (annoying to write a manual copy constructor for the entire class, just to replace a pointer with a unique_ptr), just as I had it when I still used raw pointer.

Searching for when to use shared_ptr vs. unique_ptr, I never see the simple preservation of the copy-constructor indicated as a possible key reason to use shared_ptr (possible exception https://stackoverflow.com/a/16030118/3673329 but not giving any detail), but I also do not directly see any reason why this could not be a valid choice.

I reckon shared_ptr may be a bit more resource intensive, but assume my case where this is no real problem.

*In particular, the default/shallow copying of the class was fine for my purposes as long as I used raw pointer members instead of smart ones.

FlorianH
  • 600
  • 7
  • 18

2 Answers2

2

If the only reason to use std::shared_ptr is to retain default copy constructibility of your class, you prioritize ease of use over the intended semantics of your class with respect to resource handling. In my opinion, you have to decide up front whether the class shall share its resources or exclusively own it. If you are unsure whether the class should share its resources with copied instances or not, something else in the design might at least be suspicious.

There might be an exception to this guideline, and that is std::shared_ptr<const YourType>. In the case of read only access, it might be acceptable to use such a technique to allow for default copy construction (although in a different context, here is where Sean Parent says that std::shared_ptr<const T> is acceptable to obtain value semantics).

Note one further implication: if you share a std::shared_ptr instance, you are not only sharing state and functionality of the pointee, you also share lifetime control. If the latter is not what you intend, just share a reference (preferable) or a raw pointer to the pointee, with access e.g. via a getter-like member function. If the consuming parts of your class can't know whether the pointee is still alive or has already been destroyed, a std::weak_ptr could be an option.

lubgr
  • 37,368
  • 3
  • 66
  • 117
  • Indeed, prioritizing ease of use over intended semantics in my use case. In reality the class owns its resources, but I allow it to thus 'theoretically' share its resources, just as it's the only way (other than sticking to raw pointer) I see to trivially preserve the auto copy-constructor. I interpret your answer as suggesting it is simply bad practice mostly as possibly misleading, but maybe there are no major more direct bad consequences. – FlorianH Feb 22 '19 at 11:23
  • 1
    Yes, I think your reading of the answer is correct :) One additional detail that just crossed my mind with this regard is in the answer now. – lubgr Feb 25 '19 at 07:43
0

There is still a great deal of advantage in using a unique_ptr and providing the missing copy operations manually.

You get the correctness guarantees plus an easy customisation point for inner object cloning.

example:

#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);
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Thanks. I understand there are **some** advantages of manual copy-constructor in **some** (maybe even many or most) cases. But I reckon there is a reason why c++ allows auto-copy-constructors at all. Say, simply for cases where convenience dominates possible drawbacks, e.g. as the latter are not too relevant in a particular case. And I don't see in your answer any reason why my case (one smart pointer member) would **generally** invalidate reasons that could mean I'm fine with auto-copy-constructor, or whether/why shared_ptr as simple solution to preserve auto copy-constructor is bad in itself – FlorianH Feb 22 '19 at 11:13