将std::shared_ptr传递给构造函数

20

以下方案在创建“Stuff”并将其所有权授予“Foo”的过程中是否合理且有效?

class Foo
{
    explicit Foo(const std::shared_ptr<Stuff>& myStuff)
        : m_myStuff(myStuff)
    {
    }

    ...

private:
    const std::shared_ptr<Stuff> m_myStuff;
}

std::shared_ptr<Stuff> foosStuff(new Stuff());
Foo f(foosStuff);

9
不完全正确,Foo并没有 获取 所有权。 shared_ptr 的目的是 共享 所有权。 - juanchopanza
或者更喜欢使用 std::shared_ptr<Stuff> foosStuff(new Stuff()); - stefaanv
@juanchopanza 也许您更倾向于在这种情况下使用unique_ptr? - Baz
2
@Baz 如果你真的想要拥有权,那么是的。 - juanchopanza
3个回答

30

由于您对效率感兴趣,我想提出两点:

shared_ptr<>是许多标准库类型之一,其中移动构造比复制构造更便宜。 复制shared_ptr是较慢的,因为复制需要原子地增加引用计数器,而移动shared_ptr根本不需要触及所引用的数据或计数器。从Dave Abrahams的文章“Want Speed? Pass by value!”中可以了解到,在某些情况下,通过值传递函数参数实际上是有益的。这是其中之一:

class Foo
{
  explicit Foo(std::shared_ptr<Stuff> myStuff)
  : m_myStuff(move(myStuff))
  {}

  ...

private:
  std::shared_ptr<Stuff> m_myStuff;
};

现在你可以编写

Foo f (std::make_shared<Stuff>());

如果参数是临时对象,且没有 shared_ptr 被复制(只移动一两次),那么在这里使用 std::make_shared 有一个优点:只做一次内存分配。在你的情况中,你自己分配了 Stuff 对象,并且 shared_ptr 构造函数也不得不动态分配引用计数器和删除器。而 make_shared 只需要进行一次分配就可以完成所有操作。


2
我喜欢那个make_shared函数。 - Baz
1
顺便说一下:如果你改用std::unique_ptr,实际上是必须按值传递的 ;) - sellibitze
1
@jleahy:这样做的“好处”是构造函数仍然接受shared_ptr,同时没有复制成本。当然,在这个使用示例中,我可以使用unique_ptr代替。但这可能会不必要地限制接口。如果您打算存储shared_ptr,只需接受shared_ptr作为参数,而不是unique_ptr。是否存储shared_ptr是一个好主意是另一个问题,不能简单地回答,除非知道Baz在这里想要做什么。 - sellibitze
1
重要的是要澄清你所指的 C++ 标准版本,否则用户可能会误解结论。因此,如果您可以进行移动优化,上面的答案是正确的。值得检查 Meyers-Alexandrescu-Sutter 的演讲,我在这里 https://dev59.com/XXA75IYBdhLWcg3waIQJ#8741626 中提到了。 - mloskot
@sellibitze,我对这里的使用感到困惑。链接讨论了在函数调用中使用它。而在这里,我们将其“移动”到类的成员变量中。这意味着它会随着实例的存在而存在,对吗?那么“临时”的意思是什么,这不会受到影响吗? - ajpieri
显示剩余4条评论

6

是的,这是完全合理的。这样一来,管理共享指针所涉及的工作只需要进行一次,而不是通过值传递时需要进行两次。您还可以考虑使用 make_shared 来避免复制构造。

std::shared_ptr<Stuff> foosStuff(std::make_shared<Stuff>());

你唯一能够进行的改进是,如果Foo将成为唯一的所有者(即,在创建Foo之后你不再需要保留foosStuff),那么你可以切换到使用std::unique_ptr或boost::scoped_ptr(这是C++11之前的等效工具),它们的开销更小。


好的,我应该研究学习更多关于其他智能指针的知识,因为我只熟悉shared_ptr。 - Baz
什么是scoped_ptr,它不是C++的一部分吗?也许你指的是std::unique_ptr?为什么要使用自定义删除器? - Christian Rau
@ChristianRau:我猜boost::scope_ptr是什么意思。删除器位可能是对早期(隐形)编辑的过时引用? - Kerrek SB
2
@KerrekSB 我也这么认为,但是假设并不能帮助任何阅读答案的人理解,在C++标准库中有一个完全适合的所有权指针,而不需要使用boost(甚至没有提到)。而且问题已经改变了。一旦他注意到问题的变化和不精确的信息,他答案上的负评将会神奇地消失。 - Christian Rau
@ChristianRau 我的回答因问题的更改而略有过时。如果不是因为自定义删除器,我也会像KerrebSB的回答一样更喜欢使用make_shared。我已经更新了它以防止混淆。 - jleahy
make_shared 不仅仅是为了保存一个(正式的)副本,实际上它是共享指针类型擦除体更高效的实现。 - Kerrek SB

3

使用make_foo辅助函数可能更加高效:

Foo make_foo() { return Foo(std::make_shared<Stuff>()); }

现在你可以这样说:auto f = make_foo();。或者至少自己使用make_shared调用,因为生成的shared_ptr可能比从new表达式构造的更有效率。如果Stuff实际上需要构造函数参数,则私有辅助构造函数可能是合适的:
struct Foo
{
    template <typename ...Args>
    static Foo make(Args &&... args)
    {
        return Foo(direct_construct(), std::forward<Args>(args)...);
    };

private:

    struct direct_construct{};

    template <typeaname ...Args>
    Foo(direct_construct, Args &&... args)
    : m_myStuff(std::make_shared<Stuff>(std::forward<Args>(args)...))  // #1
    {  }
};

您可以将Foo::make包装在上述的make_foo中,也可以直接使用它:

auto f = Foo::make(true, 'x', Blue);

话虽如此,除非你真的需要共享所有权,否则 std::unique_ptr<Stuff> 似乎是更可取的方法:它在概念上更简单,并且在某种程度上也更有效率。在这种情况下,你可以在标有 #1 的行中使用 m_myStuff(new Stuff(std::forward<Args>(args)...))


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接