那里有很好的答案,所以我只是添加一些忘记的东西。
0. RAII 是关于范围的
RAII 是关于两者:
- 在构造函数中获取资源(无论是什么资源),并在析构函数中取消获取它。
- 在声明变量时执行构造函数,并在变量超出范围时自动执行析构函数。
其他人已经回答了,所以我不会详细说明。
1. 使用 Java 或 C# 编码时,您已经使用 RAII...
乔丹先生:什么!当我说,“妮可,给我拖鞋,给我睡帽,”那是散文吗?
哲学大师:是的,先生。
MONSIEUR JOURDAIN:四十多年来,我一直在说散文,但对此一无所知,我非常感谢你教会了我这一点。
——莫里哀:中产阶级绅士,第 2 幕,第 4 场
正如 Jourdain 先生对散文所做的那样,C# 甚至 Java 人已经在使用 RAII,但是以隐藏的方式。例如,以下 Java 代码(在 C# 中以相同的方式编写,用 替换synchronized
)lock
:
void foo()
{
// etc.
synchronized(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
...已经在使用RAII:互斥量获取在关键字(synchronized
或lock
)中完成,退出范围时将完成取消获取。
它的符号非常自然,即使对于从未听说过 RAII 的人来说也几乎不需要解释。
在这里,C++ 相对于 Java 和 C# 的优势是可以使用 RAII 制作任何东西。synchronized
例如,在 C++ 中没有等价于nor的直接内置lock
函数,但我们仍然可以拥有它们。
在 C++ 中,它会这样写:
void foo()
{
// etc.
{
Lock lock(someObject) ; // lock is an object of type Lock whose
// constructor acquires a mutex on
// someObject and whose destructor will
// un-acquire it
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
可以很容易地用 Java/C# 方式编写(使用 C++ 宏):
void foo()
{
// etc.
LOCK(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
2. RAII 有其他用途
白兔:[唱歌]我迟到了/我迟到了/为了一个非常重要的约会。/ 没时间说“你好”。/ 再见。/ 我来晚了,我来晚了,我来晚了。
——爱丽丝梦游仙境(迪士尼版,1951)
你知道构造函数什么时候被调用(在对象声明处),你也知道它对应的析构函数什么时候被调用(在作用域的出口处),所以你可以只用一行代码编写几乎神奇的代码。欢迎来到 C++ 仙境(至少,从 C++ 开发人员的角度来看)。
例如,您可以编写一个计数器对象(我将其作为练习)并通过声明其变量来使用它,就像上面使用的锁定对象一样:
void foo()
{
double timeElapsed = 0 ;
{
Counter counter(timeElapsed) ;
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
当然,可以再次使用宏以 Java/C# 方式编写:
void foo()
{
double timeElapsed = 0 ;
COUNTER(timeElapsed)
{
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
3.为什么C++缺乏finally
?
[呼喊] 这是最后的倒计时!
— 欧洲:最后的倒计时(对不起,我没有报价,在这里... :-)
该finally
子句在 C#/Java 中用于在范围退出(通过return
异常或抛出异常)的情况下处理资源处置。
精明的规范读者会注意到 C++ 没有 finally 子句。这不是错误,因为 C++ 不需要它,因为 RAII 已经处理资源处理。(相信我,编写 C++ 析构函数比编写正确的 Java finally 子句,甚至是 C# 的正确 Dispose 方法要容易得多)。
不过,有时,一个finally
条款会很酷。我们可以在 C++ 中做到这一点吗?我们可以!再次使用 RAII。
结论:RAII 不仅仅是 C++ 中的哲学:它是 C++
雷伊?这是C++!!!
—— C++ 开发者的愤怒评论,被一个不起眼的斯巴达国王和他的 300 位朋友无耻地抄袭
当您在 C++ 方面达到一定程度的经验时,您会开始考虑RAII、构造函数和析构函数的自动执行。
您开始考虑范围,并且{
和}
字符成为您代码中最重要的字符。
几乎所有东西都适合 RAII:异常安全、互斥体、数据库连接、数据库请求、服务器连接、时钟、操作系统句柄等,最后但并非最不重要的是内存。
数据库部分是不可忽视的,因为如果你愿意付出代价,你甚至可以用“事务性编程”风格编写,执行一行又一行的代码,直到最后决定是否要提交所有更改,或者,如果不可能,则将所有更改还原(只要每行至少满足强异常保证)。(有关事务性编程,请参阅这篇Herb 的 Sutter 文章的第二部分)。
就像一个谜题,一切都合适。
RAII 是 C++ 的重要组成部分,没有它,C++ 就不可能是 C++。
这就解释了为什么有经验的 C++ 开发人员如此迷恋 RAII,以及为什么 RAII 是他们在尝试另一种语言时首先搜索的东西。
它还解释了为什么垃圾收集器虽然本身就是一项了不起的技术,但从 C++ 开发人员的角度来看并没有那么令人印象深刻:
- RAII 已经处理了 GC 处理的大部分案件
- GC 比 RAII 更好地处理纯托管对象的循环引用(通过智能使用弱指针来缓解)
- 仍然 GC 仅限于内存,而 RAII 可以处理任何类型的资源。
- 如上所述,RAII 可以做的很多很多……