【问题标题】:std::shared_ptr - Best practice for passing shared pointer as parameterstd::shared_ptr - 将共享指针作为参数传递的最佳实践
【发布时间】:2014-01-21 11:09:53
【问题描述】:

我已经远离严肃的 C++ 大约十年了。我又回来了,目前正在开展一个项目以完全熟悉 C++11。关于如何最好地通过 std::shared_ptr,我遇到了一些生存危机。

举个简单的例子,采用以下设置:

class ServiceB {
public:
    ServiceB() {}
};

class ServiceA {
public:
    ServiceA(std::shared_ptr<ServiceB>& serviceB)
        : _serviceB(serviceB) {

    }

private:
    std::shared_ptr<ServiceB> _serviceB;
};

class Root {
public:
    Root()
        : _serviceB(std::shared_ptr<ServiceB>(new ServiceB())),
        _serviceA(std::unique_ptr<ServiceA>(new ServiceA(_serviceB))) {

    }

private:
    std::shared_ptr<ServiceB> _serviceB;
    std::unique_ptr<ServiceA> _serviceA;
};

请注意,ServiceA 需要对 ServiceB 的引用。我想将该引用保留在 shared_ptr 中。我可以做我在这里所做的事情吗,这只是将 shared_ptr 作为参考传递下来,让 std::shared_ptr 复制构造函数为我完成工作?这是否正确地增加了 shared_ptr 上的引用计数?

如果这不是最好的方法,那么传递 std::shared_ptr 的常见“最佳实践”是什么?

【问题讨论】:

  • 看看Guru of the Week #91,这正是这个主题。
  • C++11 的经验法则“如果你要复制,就按值传递”适用于 shared_ptr 和其他所有类型一样。
  • 也使用 make_shared 而不是手动构建 shared_ptr。它为您节省了一次分配并防止在某些情况下发生内存泄漏。 en.cppreference.com/w/cpp/memory/shared_ptr/make_shared
  • @aryjczyk 我终于花时间查看 make_shared/make_unique 的实际工作原理,感谢您的提示。我试图像“std::make_shared(existingPtr)”一样使用它们,而不是意识到它们实际上是使用一些模板魔术构造我的对象。我仍然有一种情况我必须做 std::unique_ptr 因为我有一个现有的要包装的指针,但是我在其他任何地方都使用 make_shared/make_unique。

标签: c++ c++11


【解决方案1】:

您应该像传递其他对象一样传递共享指针。如果您需要存储一个副本(共享指针的副本,而不是指向的对象),按值传递,然后移动到其目的地。

ServiceA(std::shared_ptr<ServiceB> serviceB)
    : _serviceB(std::move(serviceB)) {}

另外,如果您不介意编写两个构造函数,您可以通过编写一个接受 const 引用并复制它的一个,以及一个接受一个 r 值引用,并移动它。

ServiceA(std::shared_ptr<ServiceB> const& serviceB)
    : _serviceB(serviceB) {}

ServiceA(std::shared_ptr<ServiceB> && serviceB)
    : _serviceB(std::move(serviceB) {}

【讨论】:

  • shared_ptr 的复制会调整引用计数(使用可能的锁定或原子操作),因此可能会移动。因此,按值传递(引用计数的一次、两次或三次更新)不会比将引用传递给 const 和复制(一次更新)快得多。照我看来。不确定这是否是您在说的。 :-)
  • @Cheersandhth.-Alf:如果 move 构造一个共享指针会调整引用计数,这不是一个严重无能的实现的标志吗?我无法想象为什么它是必要的。
  • 没有。将原始值归零或增加引用计数并保持原始值的选择(对于移动构造函数)理想情况下取决于最快的方法,而这一点也不明显。但是,正如您所说,做最慢的事情,或者不以某种理想主义或“无法想象”的理由调整参考计数,将是执行不力的标志。
  • @Alf Zeroing 避免了需要同步,这很可能是一个胜利。
  • @Cheersandhth.-Alf:我的回答只适用于“如果您需要存储一份副本”——就在第二句话中。如果你需要存储 5 个副本,那么你将不得不制作 5 个副本,并且引用计数将增加 5 倍。通过 const 引用获取参数不会改变这一事实。如果您不需要 需要任何副本,那么此成语不适用于您的情况,您可能应该通过引用 const(或非 const,取决于您的需要)来获取参数。这很有趣。
【解决方案2】:

通过值传递(编译器非常擅长删除副本)或通过 const 引用 - 非 const 引用,因为你有它看起来像你打算修改参数。

此外,对于新代码,请考虑将unique_ptr 用于有意义的参数和返回值,或者共享不是合同的一部分。 (您可以从 unique_ptr 生成 shared_ptr,但反之则不行。)

【讨论】:

  • unique_ptr 不太适合一般引用,因为它拥有所有权并破坏了所指对象。
  • @Cheersandhth.-Alf 你错过了“共享不是合同的一部分”
  • @Alf 当然——但是现在很多使用shared_ptr(作为传递所有权的安全方式)的旧代码最好用unique_ptr编写。
【解决方案3】:

当您打算修改实际参数时,通过引用传递给非常量。

当您不打算这样做时,通过引用传递给 const。

从技术上讲,对 const 的引用只是对 shared_ptr 的一种微优化,但它并没有什么害处,它是类类型参数的通用约定,它可以缩短一些纳秒时间。


另一件事,因为 C 使用前置下划线作为实现名称,最好在 C++ 中避免这种情况,因此不要前置下划线,而是将其放在末尾。例如这是 boost 库中的约定。或者只是使用其他约定。

【讨论】:

    猜你喜欢
    • 2012-06-05
    • 1970-01-01
    • 2018-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多