将函数返回的共享指针绑定到常量的左值引用是否是一种好的做法
Is it good practice to bind shared pointers returned by functions to lvalue references to const?
虽然我花了一段时间才习惯,但我现在养成了让函数通过对const
的左值引用而不是通过值来获取共享指针参数的习惯(当然,除非我需要修改原始参数,在这种情况下,我通过对非const
的左值参考来获取它们(:
void foo(std::shared_ptr<widget> const& pWidget)
// ^^^^^^
{
// work with pWidget...
}
这样做的优点是避免了共享指针的不必要副本,这意味着线程可以安全地增加引用计数,并可能导致不必要的开销。
现在我一直在想,采用某种对称的习惯来检索共享指针是否明智,这些指针是由函数的值返回的,比如在以下代码片段的末尾:
struct X
{
// ...
std::shared_ptr<Widget> bar() const
{
// ...
return pWidget;
}
// ...
std::shared_ptr<Widget> pWidget;
};
// ...
// X x;
std::share_ptr<Widget> const& pWidget = x.bar();
// ^^^^^^
采用这样的编码习惯有什么陷阱吗?一般来说,我有没有理由更喜欢将返回的共享指针分配给另一个共享指针对象,而不是将其绑定到引用?
这只是旧问题的重制版,即捕获对临时的const引用是否比创建副本更有效。简单的答案是事实并非如此。行中:
// std::shared_ptr<Widget> bar();
std::shared_ptr<Widget> const & pWidget = bar();
编译器需要创建一个本地未命名变量(非临时变量(,通过调用bar()
初始化该变量,然后将引用绑定到它:
std::shared_ptr<Widget> __tmp = bar();
std::shared_ptr<Widget> const & pWidget = __tmp;
在大多数情况下,它将避免创建引用,只在函数的其余部分中对原始对象进行别名,但最终,无论变量是被称为pWidget
还是__tmp
并进行别名处理都不会带来任何好处。
相反,对于普通读者来说,bar()
可能看起来并没有创建对象,而是生成了对现有std::shared_ptr<Widget>
的引用,因此维护人员必须找出bar()
的定义位置,以了解pWidget
是否可以在该函数的范围之外进行更改。
通过绑定到const引用进行生存期扩展是该语言中一个奇怪的功能,几乎没有实际用途(即,当引用是基引用时,您不太关心值返回的确切派生类型是什么,即ScopedGuard
(。
您可以向后进行优化:
struct X
{
// ...
std::shared_ptr<Widget> const& bar() const
{
// ...
return pWidget;
}
// ...
std::shared_ptr<Widget> pWidget;
};
// ...
// X x;
std::share_ptr<Widget> pWidget = x.bar();
由于bar
返回一个成员变量,它必须具有您版本中shared_ptr
的副本。如果通过引用返回成员变量,则可以避免复制。
这在你的原始版本和上面显示的版本中都不重要,但如果你调用:
x.bar()->baz()
在您的版本中,将创建一个新的shared_ptr
,然后调用baz。
在我的版本中,baz是直接在shared_ptr
的成员副本上调用的,并且避免了原子引用的递增/递减。
当然,shared_ptr
复制构造函数(原子增量(的成本非常小,甚至在除性能最敏感的应用程序之外的所有应用程序中都不值得注意。如果您正在编写一个性能敏感的应用程序,那么更好的选择是使用内存池体系结构手动管理内存,然后(小心地(使用原始指针。
在David Rodríguez-dribeas所说的基础上,即绑定到const
引用并不能避免复制,而且计数器无论如何都会递增,下面的代码说明了这一点:
#include <memory>
#include <cassert>
struct X {
std::shared_ptr<int> p;
X() : p{new int} {}
std::shared_ptr<int> bar() { return p; }
};
int main() {
X x;
assert(x.p.use_count() == 1);
std::shared_ptr<int> const & p = x.bar();
assert(x.p.use_count() == 2);
return 0;
}
- 将错误返回给调用方而不是立即在 C++ 中抛出错误是否是一种好的做法
- 将相同共享指针的副本存储在不同的向量中是否是一种好的做法?
- 使用类在C++中存储和列出变量/方法是否是一种好的做法
- 如果 C 函数仍然可以间接执行(通过回调函数),那么将它声明为静态函数是否是一种不好的做法?
- 从 std::string 到 std::array<char,size> 的 memcopy 额外数据是否是一种未定义的行为?
- 在类方法中使用 "this" 指针是否是一种好的做法?
- 派生类是单例是否是一种好的做法
- 即使在多任务处理时,添加用户时间 + 系统时间(来自 shell 的时间命令)是否是一种可靠的措施?
- 为许多类可能需要的所有常量变量制作独立的头文件是否是一种很好的做法?
- 移动 l 值参考参数是否是一种不好的做法?
- 返回对私有向量成员元素的非常量引用是否是一种不好的做法
- 禁止模板虚拟功能是否是一种不必要的谨慎
- 从 r 值引用限定方法返回 r 值引用是否是一种好的做法?
- C++ - 在类中包含不是类对象属性的变量是否是一种不好的做法
- 如果要调用多个成员函数,对象编辑器是否是一种好方法?
- 使用模板重载现有函数是否是一种不好的做法
- 在编译时检查模板参数是否是一种字符串
- 使用此"shortcutting function"是否是一种好的做法?
- 在无效函数中使用退出来纠正错误是否是一种好的做法
- 在声明中省略参数名称是否是一种好的做法