将函数返回的共享指针绑定到常量的左值引用是否是一种好的做法

Is it good practice to bind shared pointers returned by functions to lvalue references to const?

本文关键字:是否是 一种 引用 共享 返回 函数 指针 绑定 常量      更新时间:2023-10-16

虽然我花了一段时间才习惯,但我现在养成了让函数通过对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;
}
相关文章: