【问题标题】:Why implementing swap() as non-throwing为什么将 swap() 实现为非抛出
【发布时间】:2017-05-18 08:08:25
【问题描述】:

我确实理解为什么有时建议为给定的类实现我们自己的 swap() 函数。

例如,如果我们有一个遵循 pimpl 习语的类,我们可能希望定义自己的 复制构造函数,以便它执行 深度复制 作为参数传递的对象的内容,而不是 默认复制构造函数 执行的 浅复制。这同样适用于 复制赋值运算符

因为std::swap() 似乎是根据(至少在涉及 C++03 时)复制构造函数复制赋值运算符实现的>。对要交换的对象执行深拷贝是低效的,因为只需要交换这些对象包含的指针即可。

我的问题是为什么我们应该将swap() 函数实现为非抛出函数。

在上面解释的情况下,我假设它只是语义:因为没有分配新资源(即:两个现有指针只是被交换)。这种函数抛出异常没有多大意义。

但是,在这个推理中,我可能忽略了其他原因或场景。

【问题讨论】:

  • 反问:如果没有必要,为什么要实现抛掷功能?必须处理异常。您必须决定要做什么,并在调用该函数时使用 try-catch 保护代码。这是一些工作。如果有办法完全避免它,为什么不呢?
  • 1. 不投掷是什么意思?你的意思是throw()? 2. 在问为什么 swap 不应该抛出之前,先问一下:should swap 应该不抛出吗?
  • 交换什么时候抛出?
  • 我的意思是,函数从不抛出。不是 throw() 作为函数签名的一部分。

标签: c++ c++03


【解决方案1】:

我的问题是为什么我们应该将我们的swap() 函数实现为一个非抛出

  1. 因为swap如果可能会抛出是完全没用的。

    考虑:你swap 两个实例,操作抛出。现在,他们处于什么状态?

    强有力的保证是,如果抛出异常,则不会产生副作用,这意味着两个原始对象都保持其原始状态。

    如果我们不能满足强保证,在很多情况下我们根本就不能使用swap,因为没有办法从故障中有效地恢复,而且写出来也没有意义swap 的那个版本。

  2. 因为它没有理由抛出。

    swap(现在)的简单实现使用移动赋值和构造。

    通常没有理由抛出移动构造函数:它不会分配任何新的东西,它只是重新安置现有数据。通常没有理由抛出移动赋值(如上所述),析构函数永远不应该抛出 - 而这些是唯一需要的操作。

【讨论】:

  • 从第一点开始,如果类型 A 有一个潜在的抛出交换,它会使组合变得困难。如果类型 B 有两个 A 成员,则无法安全交换 B::swap(B&),因为任何一个 A 都可能抛出并使 B 处于未知且不可恢复的状态。
  • 关于第一点。如果 copy 构造函数 可能抛出但 copy assignment operator 不会抛出怎么办?在这种情况下swap() 可能会抛出,但不会丢失强力保证
  • 不正确 - 您可以成功交换 B 的两个 A 成员中的第一个,然后再扔第二个。在这种情况下,您的 B 对象处于可能无法使用的状态。无论如何,您现在应该使用 move 变体,而且很少有充分的理由让它们抛出。
【解决方案2】:

编辑

我最初将问题理解为“为什么要在 swap 函数上使用 throw 说明符。这个答案可能离题了,因为我没有解释为什么 swap 永远不会抛出。


我认为最好的答案是为什么不呢?

当这个函数没有理由抛出时,你为什么不指定一个函数永远不会抛出?

当函数没有理由抛出异常时,你应该始终将函数实现为非抛出:你为你的函数提供了更强的保证。

此外,通过一些元编程,您可以利用不抛出函数的优势。当交换/复制/移动(C++11)是无抛出时,一些 STL 类使用它来获得更快的成员函数。 (实际上我不确定他们是否利用了在 C++11 之前的代码中没有抛出的函数)

【讨论】:

    【解决方案3】:

    对于一些类如

    遵循 pimpl 习语的类

    我们知道swap的实现不需要抛出,因为

    这种函数抛出异常没有多大意义。

    当抛出异常没有意义时,最好不要抛出异常。


    可能还有其他类,例如那些包含复杂成员但没有专门的交换函数但可能会抛出复制构造函数/赋值的类。对于这样的类,我们不能实现从不抛出的交换。

    【讨论】:

    • 所以,实现我们自己的swap() 函数不仅关乎性能,还关乎异常安全,即:从可能抛出的swap() 函数转移到一个那不会。 std::swap() 的非专用版本可能会抛出,因为可能会分配资源。
    • @Neroku 非常敏锐的观察力。
    • swap() 的非专业版本现在不太可能抛出,它使用移动构造和移动分配(如果存在)。
    • @Useless now 准确地说是因为 C++11
    【解决方案4】:

    swaping 你的 pImpls can't fail,在一个格式良好的程序中。 (并且格式错误的程序中的行为无关紧要)。没什么可扔的

    【讨论】:

      猜你喜欢
      • 2023-03-21
      • 1970-01-01
      • 2011-08-30
      • 2011-05-16
      • 1970-01-01
      • 1970-01-01
      • 2020-06-22
      • 2022-07-01
      • 2013-05-13
      相关资源
      最近更新 更多