除了你的特定情况下不需要在构造函数中抛出之外,事实上
pthread_mutex_lock
如果你的互斥锁没有被初始化,实际上会返回一个EINVAL,并且你可以在调用
lock
后抛出异常,就像在
std::mutex
中所做的那样。
void
lock()
{
int __e = __gthread_mutex_lock(&_M_mutex);
if (__e)
__throw_system_error(__e);
}
一般来说,构造函数中的抛出操作对于在构造过程中发生的获取错误是可以接受的,并且符合 RAII(资源获取即初始化)编程范式。请参考
RAII示例。
void write_to_file (const std::string & message) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
std::ofstream file("example.txt");
if (!file.is_open())
throw std::runtime_error("unable to open file");
file << message << std::endl;
}
关注以下语句:
static std::mutex mutex
std::lock_guard<std::mutex> lock(mutex);
std::ofstream file("example.txt");
第一条语句是 RAII 并且 noexcept
。在(2)中,明显地,RAII 应用于 lock_guard
上,它实际上可以 throw
,而在(3)中,ofstream
似乎不是 RAII,因为必须通过调用 is_open()
来检查对象的状态,该函数检查了 failbit
标志。
乍一看,似乎还没有确定什么是 标准方式,并且在第一种情况下,与 OP 实现相比,std::mutex
在初始化时不会抛出异常。在第二种情况下,无论从 std::mutex::lock
中抛出什么异常,它都会抛出异常,而在第三种情况下,则完全没有抛出异常。
请注意这些差异:
(1) 可以声明为静态变量,实际上将被声明为成员变量
(2) 实际上不需要声明为成员变量
(3) 应该声明为成员变量,底层资源可能并不总是可用。
所有这些形式都是RAII的;要解决这个问题,必须分析RAII。
- 资源:您的对象
- 获取(分配):创建您的对象
- 初始化:您的对象处于其不变状态
这并不要求您在构造时初始化和连接所有内容。例如,当您创建网络客户端对象时,实际上不会在创建时将其连接到服务器,因为这是一个速度缓慢且容易出错的操作。相反,您应该编写一个connect
函数来完成这个任务。另一方面,您可以创建缓冲区或仅设置其状态。
因此,您的问题归结为定义初始状态。如果在您的情况下,您的初始状态是
必须初始化互斥锁,那么您应该从构造函数中抛出异常。相反,不初始化也可以(就像在
std::mutex
中所做的那样),并将不变状态定义为
已创建互斥锁。无论如何,其成员对象的状态并不一定会影响其不变式,因为
mutex_
对象通过
Mutex
公共方法
Mutex::lock()
和
Mutex::unlock()
在
locked
和
unlocked
之间变化。
class Mutex {
private:
int e;
pthread_mutex_t mutex_;
public:
Mutex(): e(0) {
e = pthread_mutex_init(&mutex_);
}
void lock() {
e = pthread_mutex_lock(&mutex_);
if( e == EINVAL )
{
throw MutexInitException();
}
else (e ) {
throw MutexLockException();
}
}
};
std::lock_guard
的类似实现。 - Laurent Grégoirelock
和unlock
,这样你的互斥类型才能与std::lock_guard
一起工作;他正在重新实现std::mutex
,而不是std::lock_guard
,这就是C++标准库中这两个类分开的原因。 - ShadowRanger