【问题标题】:Is this item from Effective Modern C++ still up to date?Effective Modern C++ 中的这个项目仍然是最新的吗?
【发布时间】:2020-10-17 13:09:12
【问题描述】:

有效的现代 C++ 第 146 页:

void processWidget(std::shared_ptr<Widget> spw, int priority);
void cusDel(Widget *ptr);//a custom deleter

这是 C++17 之前的不安全调用:

processWidget(std::shared_ptr<Wdiget>(new Widget, cusDel), computePriority());

它曾经是不安全的,因为computePriority 可以在new Widget 之后但在std::share_ptr 构造函数之前调用,并且如果computePriority 产生异常,动态的allcoat Widget 将被泄露。 因此你可以这样做:

std::shared_ptr<Widget> spw(new Widget, cusDel);
processWidget(spw, computePriority());

现在这将在 shared_ptr 上添加一个复制构造函数操作。所以你也可以这样做:

std::shared_ptr<Widget> spw(new Widget, cusDel);
processWidget(std::move(spw), computePriority());

所以我的问题是,下面的代码是否仍然能够在 C++17 中泄漏内存?

processWidget(std::shared_ptr<Wdiget>(new Widget, cusDel), computePriority());

我读过thisthis 但我仍然不确定,我相信std::shared_ptr&lt;Wdiget&gt;(new Widget, cusDel)computePriority() 都在调用processWidget 之前排序,但是我认为computePriority() 仍然可以抛出一个在new 之后和shared_ptr 获得新创建对象的所有权之前的异常。

【问题讨论】:

  • 好的,我的立场是正确的。顺便说一句,今天我对 C++ 的喜爱并没有增加。
  • @tevemadar 很遗憾听到这个消息。我希望您只会对一些 c++ 程序员有这种感觉。这种语言实际上很酷:)
  • @cigien 我没有说任何关于程序员使用 C++ 的事情,但那个例子是int y = operator&lt;&lt;(S(j=1), j=2);。尽管括号有数学意义,也许还有常识,但最后似乎对j 的不确定性感到有些自豪。这有点像 8/2(2+2) 模因,只是它变得真实。
  • @tevemadar 我不确定这是不是骄傲,这只是对语言提供的保证的认可。
  • @cigien 在您的评论之后我刚回来,然后重新阅读了这件事。我在这里评论错误的部分。我不关心j 的不确定性,这只是一个评估顺序,哪个参数(operator&lt;&lt;())排在第一位,我可以忍受“随机”。我关心S() 的“随机”排序。如果j 是一个全局变量,这种不确定的顺序性质可能意味着S()1 作为其参数,但看到j=2 作为变量。其实那是我不喜欢的。

标签: c++ c++17 undefined-behavior exception-safety


【解决方案1】:

C++17 确实改变了用于调用函数的表达式的评估顺序。虽然它没有强加任何特定的顺序,it does say:

参数的初始化,包括每个相关的值计算和副作用,相对于任何其他参数的初始化是不确定的。

“不确定排序”is defined as

当 A 在 B 之前排序或 B 在 A 之前排序时,评估 A 和 B 的排序不确定,但未指定哪个。

因此一个参数将在另一个参数之前初始化,包括所有副作用。所以第一个参数或第二个参数在另一个之前被完全评估。

【讨论】:

  • 所以示例中对processWidget 的调用不再是UB,对吧?
【解决方案2】:

建议而不是直接回答您的问题。

我将适用于这个案例的一个原则:如果一个陈述的安全性不明显,那么就不要假设它 - 即使语言律师可以为您找到适当的保证。做出微妙假设的代码通常不是一个好主意。如果有人由于某些兼容性限制而尝试使用 C++14 编译它怎么办?他们不会收到警告。

相反,您可以通过使用两行解决方案(使用 std::move)或编写 make_shared() 的版本来省去麻烦,它也采用自定义删除器 - 可能是这样的:

template< class T, class Deleter, class... Args >
shared_ptr<T> make_shared_with_deleter( Deleter d, Args&&... args )
{
    return std::shared_ptr<T>(new T(std::forward(args)...), d );
}

然后调用:

processWidget(nonstd::make_shared_with_deleter<Widget>(cusDel), computePriority());

另请参阅关于 tuple destruction order 的相同论点。

【讨论】:

  • 标准化参数评估的全部意义在于确保人们可以依赖它,以便他们不必编写这样的功能。
  • @NicolBolas:实际上,无论如何,我都会尽量避免在一些长表达式的中间使用原始的new。将某些细微之处标准化可能会有一些好处,但依赖这些细微之处仍然不一定是好的做法。
  • 这与new 无关。它可以是任何函数调用或评估。考虑包扩展或折叠表达式而不是范围表达式。如果没有关于评估排序的一些基本保证,您将无法有效地使用它们。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 2012-07-20
相关资源
最近更新 更多