否,除了交换和移动具有互斥锁的类
noexcept swap and move for classes with mutexes
一般来说,声明交换和移动NO是一个很好的做法,因为这可以提供一些异常保证。 同时,编写线程安全类通常意味着添加互斥锁,保护内部资源免受争用的影响。 如果我想为这样的类实现交换函数,简单的解决方案是以安全的方式锁定交换的两个参数的资源,然后执行资源交换,例如,在回答这个问题时明确回答:使用 std::mutex 实现类的交换。
这种算法的问题在于互斥锁不是noexcept,因此swap不能,严格来说,noexcept。有没有解决方案可以安全地用互斥锁交换类的两个对象?
我想到的唯一可能性是将资源存储为句柄,以便交换成为可以通过原子方式完成的简单指针交换。 否则,人们可以将锁定异常视为不可恢复的错误,无论如何都应该终止程序,但这种解决方案感觉只是一种将灰尘放在地毯下的方法。
编辑:正如评论中所说,我知道互斥体抛出的异常不是任意的,但这个问题可以这样改写:
是否有可靠的实践来限制互斥锁在实际上是不可恢复的操作系统问题时可能抛给那些的情况? 我想到的是,在交换算法中,检查要交换的两个对象是否不同。这是一个明显的死锁情况,在最佳情况下会触发异常,但可以轻松检查。 是否有其他类似的触发器可以安全地检查以使交换功能健壮并且实际上没有除了所有重要情况之外?
在 POSIX 系统上,std::mutex
通常是围绕pthread_mutex_t
的薄包装器,在以下情况下,锁定和解锁功能可能会失败:
- 试图获取已拥有的锁
- 互斥对象未初始化或已被销毁
以上两者都是C++的UB,甚至不能保证POSIX会退货。在 Windows 上,如果std::mutex
是SRWLOCK
的包装器,则两者都是 UB。
因此,似乎允许抛出lock
和unlock
函数的要点是发出程序中错误的信号,而不是让程序员期望并处理它们。
推荐的锁定模式证实了这一点:析构函数~unique_lock
是noexcept(true)
的,但应该调用noexcept(false)
unlock
。这意味着如果函数抛出unlock
异常,整个程序将被终止std::terminate
。
该标准还提到这一点:
成员报告的错误代码(如果有(的错误条件 互斥锁类型的功能应为:
(4.1( —
resource_unavailable_try_again
— 如果有任何本机句柄类型 "已操纵"不可用。(4.2( —
operation_not_permitted
— 如果线程没有 执行操作的权限。(4.3( —
invalid_argument
— 如果任何本机句柄类型作为 部分互斥体构造不正确
从理论上讲,您可能会遇到operation_not_permitted
错误,但发生这种情况的情况并未在标准中真正定义。
因此,除非您在程序中导致与std::mutex
使用相关的 UB,或者在某些特定于操作系统的场景中使用互斥锁,否则lock
和unlock
的质量实现永远不应该抛出。
在常见的实现中,至少有一个可能是低质量的:std::mutex
在旧版本的Windows(我认为Windows XP及更早版本(中CRITICAL_SECTION
之上实现,在争用期间未能延迟分配内部事件后可能会抛出。另一方面,即使是更早的版本也在初始化期间分配了此事件以防止以后失败,因此构造函数可能需要抛出std::mutex::mutex
(即使它在标准中noexcept(true)
(。
- 复制和交换习惯用法与移动操作之间的交互
- C++ - 没有自定义交换功能的移动分配运算符?
- 否,除了交换和移动具有互斥锁的类
- 是否可以通过使用移动/交换 c++11 来延长返回的临时变量的生命周期
- 将流的缓冲区与NULLPTR交换以模拟移动
- C++移动分配可防止复制交换习惯用法
- 为什么标准在移动分配运算符中使用交换?
- 交换和移动无限递归
- C++ 通过移动而不是复制来交换数组元素
- 交换对象不实现移动语义时的交换函数
- 排序链表-移动节点还是交换数据成员
- 为什么在boost ::交换中将swap_impl移动到单独的名称空间
- 复制、移动、交换、赋值和析构函数的C++继承?我需要哪个
- 移动分配与标准复制和交换不兼容
- 如何在C++中获取快速排序的移动,交换和比较计数
- 在赋值移动运算符内部使用交换
- 移动构造函数和赋值操作符,使用复制-交换习惯实现
- 在C++11中,复制和交换习语应该变成复制和移动习语吗
- std::交换不可复制但可移动的结构
- 移动语义==自定义交换函数已过时