C++17中std::make_shared()的更改

28
cppref中,直至C++17,以下内容成立:

f(std::shared_ptr<int>(new int(42)), g())的代码可能会导致内存泄漏,如果gnew int(42)之后被调用并且抛出异常,而f(std::make_shared<int>(42), g())则是安全的,因为两个函数调用永远不会交错。

我想知道在C++17中引入了哪些更改使得这不再适用。

1
类似问题的答案在这里:https://dev59.com/yVkT5IYBdhLWcg3wf_pN - 273K
那么...我们不再需要make_shared了吗?(虽然它仍然很方便) - user541686
@Mehrdad 还有其他好处。我链接的cppref页面上讨论了权衡。 - Lingxi
2个回答

20

P0145R3文件(已被C++17接受)细化了几个C++结构的求值顺序,其中包括

后缀表达式从左到右进行求值。这包括函数调用和成员选择表达式。

具体而言,该文件在标准的5.2.2/4段落中添加了以下文本:

后缀表达式在表达式列表和任何默认参数之前进行排序。与参数初始化及其本身相关的每个值计算和副作用以及初始化本身都在与任何后续参数的初始化相关的每个值计算和副作用之前进行排序。


13
哇,我完全没有关注过C++17,这次的改变实在是令人吃惊!当然,如果我们都做好了自己的工作,这些改变应该对我们来说是完全透明的,但是......哇。 - Lightness Races in Orbit
2
稍等,open-std.org页面的摘要与您引用的论文文本不符。其中一个说现在顺序是不确定的(而不是未指定的)-这意味着评估不能交错(我认为这已经是C++11中的情况了)。该论文本身确实使用了“后续”一词,这表明顺序相当确定。到底是哪个! - Lightness Races in Orbit
4
好的,我认为改进的是嵌套行为。因此我们可以保证,当 g() 发生时,如果 new 已经发生,那么 shared_ptr 的构造也已经发生了。但是,shared_ptr 构造和 g() 之间的顺序仍然未知。 - Lightness Races in Orbit
3
我怀疑参数的求值顺序不会因为gcc/clang使用从右向左,而MSVC使用从左向右,而发生改变。我也怀疑任何一方都不愿意改变这种顺序;很可能已经有软件依赖于它们当前的行为。 - Matthieu M.
2
我认为xskxzr用人类语言更直接、更清晰地回答了我的问题,而不是使用晦涩的标准文本,因此我将接受他的答案。不过,你的答案以及评论区的讨论非常有用,值得点赞 :) - Lingxi
显示剩余2条评论

20
函数参数的求值顺序已被P0400R0更改。
在更改之前,函数参数的求值相对于彼此是无序的。这意味着g()的求值可能会插入到std::shared_ptr<int>(new int(42))的求值中,从而导致您引用上下文中描述的情况。
更改后,函数参数的求值是不确定顺序的,没有交错,这意味着std::shared_ptr<int>(new int(42))的所有副作用要么发生在g()的副作用之前,要么发生在之后。现在考虑g()可能抛出的情况。
  • 如果std::shared_ptr<int>(new int(42))的所有副作用都发生在g()的副作用之前,则分配的内存将由std::shared_ptr<int>的析构函数释放。

  • 如果std::shared_ptr<int>(new int(42))的所有副作用都发生在g()的副作用之后,则根本没有内存分配。

在任何情况下,都不会再有内存泄漏。


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