【问题标题】:Is it sensible to use shared_ptr instead of unique_ptr as class member just to avoid implicit copy constructor deletion?为了避免隐式复制构造函数删除,使用 shared_ptr 而不是 unique_ptr 作为类成员是否明智?
【发布时间】:2019-02-19 12:07:05
【问题描述】:

我想保留一个大型(但不是特别复杂*)类的默认复制构造函数,但理想情况下,我想用智能指针替代一些原始指针成员。

unique_ptr 似乎是这个的默认值,但它隐式删除了我的类的复制构造函数。

shared_ptr 将允许我保留类的复制构造函数。这可能是坚持使用 shared_ptr 的一个很好的理由,即使我并不真正想要“共享”资源;我真的只想保留现成的复制构造函数(烦人为整个类编写手动复制构造函数,只是用 unique_ptr 替换指针),就像我仍然使用原始指针时一样。

搜索何时使用 shared_ptr 与 unique_ptr,我从来没有看到简单保存复制构造函数是使用 shared_ptr 的一个可能的关键原因(可能的例外https://stackoverflow.com/a/16030118/3673329,但没有提供任何细节),但我也这样做没有直接看到为什么这不是一个有效的选择。

我认为 shared_ptr 可能会占用更多资源,但假设我的情况并没有真正的问题。

*特别是,只要我使用原始指针成员而不是智能指针成员,类的默认/浅复制就可以满足我的目的。

【问题讨论】:

  • 你如何为你的类定义复制操作,指向的对象会发生什么?谁拥有该对象?
  • 不,移动就足够了。
  • 唯一指针和共享指针之间的选择不应该与类的构造函数有关。如果您的资源仅由一个所有者使用,则它的唯一 ptr.否则是共享的。如果您必须显式实现一个进行深度复制的复制构造函数 - c'est la vie.
  • @rustyx:谢谢。我在一个简单的示例中确认您所写的内容(小 A 类,具有 unique_ptr 等)。但是,当我改为使用我的真实(更大)A 时,我从 emplace_back(...) 中得到“‘A(const A&)’被隐式删除”以及 unique_ptr 成员已删除默认复制构造函数的抱怨。将 unique_ptr 替换为 shared_ptr 时,一切正常。

标签: c++ shared-ptr copy-constructor unique-ptr


【解决方案1】:

如果使用std::shared_ptr 的唯一原因是保留类的默认复制构造性,那么在资源处理方面,您优先考虑易用性而不是类的预期语义。在我看来,你必须预先决定班级是共享资源还是独占资源。如果您不确定该类是否应该与复制的实例共享其资源,那么设计中的其他内容可能至少是可疑的。

本指南可能有一个例外,那就是std::shared_ptr<const YourType>。在只读访问的情况下,使用这种技术来允许默认复制构造可能是可以接受的(尽管在不同的上下文中,here 是 Sean Parent 所说的std::shared_ptr<const T> 是可以接受的以获得值语义)。

请注意另一个含义:如果您共享std::shared_ptr 实例,您不仅共享被指点者的状态和功能,还共享生命周期控制。如果后者不是您想要的,只需共享一个引用(首选)或指向指针的原始指针,访问例如通过类似 getter 的成员函数。如果你的类的消费部分不知道指针是仍然活着还是已经被销毁,std::weak_ptr 可能是一个选项。

【讨论】:

  • 确实,在我的用例中,易用性优先于预期语义。实际上,该类拥有它的资源,但我允许它因此“理论上”共享它的资源,就像它是我看到的唯一方法(除了坚持原始指针)一样,可以轻松地保留自动复制构造函数。我将您的回答解释为暗示这只是一种不好的做法,主要是因为可能具有误导性,但也许没有更直接的重大不良后果。
  • 是的,我认为您对答案的阅读是正确的 :) 在这方面我刚刚想到的另一个细节现在在答案中。
【解决方案2】:

使用 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);
}

【讨论】:

  • 谢谢。我知道在 some (甚至可能很多或大多数)情况下手动复制构造函数有 some 优点。但我认为 C++ 完全允许自动复制构造函数是有原因的。说,只是为了方便的情况下可能的缺点占主导地位,例如。因为后者在特定情况下不太相关。而且我在您的回答中没有看到任何理由说明我的案例(一个智能指针成员)通常会导致我对自动复制构造函数很好,或者是否/为什么 shared_ptr保留自动复制构造函数的简单解决方案本身就是不好的
猜你喜欢
  • 2019-06-16
  • 2011-06-23
  • 1970-01-01
  • 2011-08-07
  • 2015-10-01
  • 1970-01-01
  • 2013-01-16
相关资源
最近更新 更多