std::shared_ptr - Best practice for passing shared pointer as parameter
我离开认真的C ++已有十年了。 我将回到正题,目前正在致力于使C ++ 11完全熟悉的项目。 我在如何最好地传递std :: shared_ptr周围存在一些生存危机。
举一个简单的例子,进行以下设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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的常见"最佳实践"是什么?
-
看一下本周的大师91号。
-
C ++ 11的经验法则"如果要进行复制,则按值传递"对shared_ptr一样适用于所有其他类型。
-
还可以使用make_shared而不是手动构造shared_ptr。它为您节省了一次分配,并在某些情况下防止内存泄漏。 zh.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的所有其他地方。
-
相关stackoverflow.com/questions/3310737/
-
@Cubbi:不幸的是,这两个演讲似乎都是在2010年进行的,当时移动语义已经非常普遍。正如赫伯本人在随后的谈话中所指出的那样,给定动作语义,他们关于通过值传递几乎"永远不正确"的主张已经过时。不过,感谢您提供的链接,这些视频中有很多不错的东西。
您应该完全像绕过其他对象一样绕过共享指针。如果需要存储(共享指针的副本,而不是指向对象的指针的)副本,请按值传递,然后移至其目的地。
1 2
| ServiceA(std::shared_ptr<ServiceB> serviceB)
: _serviceB(std::move(serviceB)) {} |
另外,如果您不介意编写两个构造函数,则可以编写一个采用const引用并复制它的实例,而使用一个r-作为实例,从而节省一点性能(一次调用共享指针的move构造函数)。值参考,并将其移动。
1 2 3 4 5
| ServiceA(std::shared_ptr<ServiceB> const& serviceB)
: _serviceB(serviceB) {}
ServiceA(std::shared_ptr<ServiceB> && serviceB)
: _serviceB(std::move(serviceB) {} |
-
复制shared_ptr会调整引用计数(可能使用锁定或原子操作),因此可能会移动。因此,按值传递(对引用计数进行一次,两次或三个更新)不能比将引用对常量进行传递和复制(一次更新)快得多。照我看来。不知道这是否是您在谈论的内容。 :-)
-
@ Cheersandhth.-Alf:如果构造共享指针的举动调整了引用计数,这是否表示实施严重不称职?我无法想象为什么会有必要。
-
没有。 (对于move构造函数而言)将原始文件清零或增加引用计数并保留原始文件的选择理想地取决于最快的操作,而那根本就不明显。如您所说,执行最慢的操作或不基于某些想法或"无法想象"的理由来调整引用计数,这将是不称职的实现的标志。
-
@Alf Zeroing避免了需要同步的可能,这可能是一个双赢。
-
@AlanStokes:假设是这样,除了复制和单个引用计数更新(将引用传递给const)之外,它仍然存在。 :-)但是这个假设是可疑的。
-
@ Cheersandhth.-Alf:另一个问题是,通过const引用传递的内容并不总是对引用计数的一个更新。如果传入了l值,则仅更新一次。但是,传入r值时,将进行不必要的复制和销毁,从而导致对引用计数的3次更新。这就是为什么这个习惯用法首先存在的原因。
-
@BenjaminLindley:优化右值情况是右值引用超载传递的内容。因此,无论采用哪种方案。
-
@ Cheersandhth.-Alf:这就是为什么这个答案分为两个部分的原因。顶部假设您只想编写一个构造函数。在我看来,在这种情况下,"按价值先行动"是首选。
-
上面的两个答案都很好,但是我发现对于添加的move构造器实现来说,这个更完整了,因此我将其标记为一个。
-
@BenjaminLindley:如果"您只想编写一个构造函数",那么对于左值参数,按值传递不能更快,并且很可能会慢一点。它不能作为一般惯例使用。因此,除了时髦之外,显然没有其他理由,无论是在手头有无变化的情况下,都做最新的事情。
-
@ Cheersandhth.-Alf:是的,l值不能更快。我从未说过可以。构造函数是在r值和l值的高性能之间取得最佳折衷。它基于一般的假设,即移动便宜,而复制昂贵。并非总是正确的假设,但在shared_ptr的情况下是正确的假设。
-
@BenjaminLindley:一般而言,它的效率很低。考虑向下传递5个函数调用shared_ptr。通过值传递,即ref计数的5次更新,涉及5个线程同步(可能通过原子同步,但仍然),而通过引用传递const,其0更新为0同步。由于我们已经省去了手头的情况(左值,因此不宜通过值传递),现在也有了通常情况下指针通常不被存储而只是传递的一般情况,因此我看不到任何优势做盲目被视为现代的事情。
-
@ Cheersandhth.-Alf:我的回答仅适用于"如果您需要存储副本",即第二句。如果需要存储5个副本,则必须制作5个副本,并且参考计数将增加5次。通过const引用接收参数不会改变这一事实。如果您不需要任何副本,则该惯用语不适用于您的情况,您应该通过引用const(或非const,取决于您的需要)来接受该参数。这好有趣。
-
即,您的答案不仅适用于当前的OP案例(传递左值以进行复制),也不适用于一般情况(传递L值左右),而是适用于类似于OP案例的部分案例,但关键细节除外基本原理(将值和值的混合传递给调用方)。出于某种原因,您认为那是"有趣"。我必须停止尝试使SO有意义。
-
@ Cheersandhth.-Alf:对不起,您现在对我没有任何意义。在OP的情况下,他将存储共享指针的副本。因此,据我所知,我的回答确实确实适用于OP案件。我指的是有趣的事,就是你我之间来回的交流。
-
@ Cheersandhth.-Alf:没关系,我明白你现在在说什么。您正在引用Root类,将其左值shared_ptr传递给另一个对象的构造函数。好的,您说对了,在这个特定的用例中没有帮助。但是,仅设计ServiceA类的类构造函数,以便针对另一个类(Root)使用它的方式进行优化(狭窄),而忽略其他类和函数可能如何使用它,则没有任何意义。
-
@ Cheersandhth.-Alf:最后,它确实适用于一般情况,您只是做了错误的概括。在这种情况下,一般情况不是"传递l值到周围",而是"传递值(r或l)以存储的副本"。
-
嗯,这很可能就是我们意见分歧的地方。我认为这两个类紧密结合在一起,除了确切显示的内容外,别无选择。这只是各种方式的优缺点,这是有争议的。好的。结束...其他。 .-)
当您打算修改实际参数时,按引用传递给非常量。
当您不希望通过引用传递给const时。
从技术上讲,对const的引用只是对shared_ptr的微优化,但这没有害处,它是类类型参数的通用约定,并且可以节省几纳秒的时间。
另一回事,由于C为实现名称使用前缀下划线,因此最好避免在C ++中使用,因此,不要在字符串前加下划线。例如那是boost库中的约定。或者只是使用其他约定。
要么按值传递(编译器非常擅长复制副本),要么按const引用-非const引用,因为这使您看起来好像打算修改参数。
同样对于新代码,请考虑在参数有意义的地方或共享不属于合同的范围内,对参数和返回值使用unique_ptr。 (您可以从unique_ptr生成shared_ptr,反之亦然。)
-
unique_ptr不太适合一般参考,因为它拥有所有权并破坏了参考对象。
-
@ Cheersandhth.-Alf如果共享不是合同的一部分,您错过了
-
@Alf当然-但是现在使用shared_ptr(作为传递所有权的一种安全方法)的许多旧代码现在最好用unique_ptr编写。