我注意到RAII在Stackoverflow上受到了很多关注,但在我的圈子里(主要是C++),RAII就像问什么是类或析构函数一样显而易见。
因此,我真的很好奇这是因为我每天都被硬核C++程序员包围,RAII在一般情况下(包括C++)并不那么出名,还是因为我现在接触的程序员没有接受过C++培训,在其他语言中人们根本不使用/知道RAII?
我注意到RAII在Stackoverflow上受到了很多关注,但在我的圈子里(主要是C++),RAII就像问什么是类或析构函数一样显而易见。
因此,我真的很好奇这是因为我每天都被硬核C++程序员包围,RAII在一般情况下(包括C++)并不那么出名,还是因为我现在接触的程序员没有接受过C++培训,在其他语言中人们根本不使用/知道RAII?
T RAIIWrapper<T>(Func<DbConnection, T> f){
using (var db = new DbConnection()){
return f(db);
}
}
虽然不如C++-RAII那么好,但它实现了大致相同的功能。每当我们需要一个DbConnection时,我们必须调用这个辅助函数,它保证在之后被关闭。
我经常使用C++的RAII技术,但我也长期开发Visual Basic 6,在那里RAII一直是一个广泛使用的概念(尽管我从未听说过有人这样称呼它)。
事实上,许多VB6程序相当依赖RAII。我经常看到下面这个小类被反复使用:
' WaitCursor.cls '
Private m_OldCursor As MousePointerConstants
Public Sub Class_Inititialize()
m_OldCursor = Screen.MousePointer
Screen.MousePointer = vbHourGlass
End Sub
Public Sub Class_Terminate()
Screen.MousePointer = m_OldCursor
End Sub
使用方法:
Public Sub MyButton_Click()
Dim WC As New WaitCursor
' … Time-consuming operation. '
End Sub
一旦耗时操作结束,原始光标将自动恢复。
RAII代表资源获取即初始化。这并不是与语言无关的。这个口号存在是因为C++的工作方式。在C++中,只有在构造函数完成之后才会构造对象。如果对象没有成功构造,则析构函数将不会被调用。
简单来说,构造函数应该确保它能够处理无法完全执行其工作的情况。例如,在构造过程中发生异常时,构造函数必须优雅地处理它,因为析构函数不会帮助解决这个问题。通常通过在构造函数中处理异常或将此麻烦转发给其他对象来实现。例如:
class OhMy {
public:
OhMy() { p_ = new int[42]; jump(); }
~OhMy() { delete[] p_; }
private:
int* p_;
void jump();
};
如果构造函数中的jump()
调用抛出异常,我们会遇到麻烦,因为p_
将泄漏。我们可以通过以下方式解决这个问题:
class Few {
public:
Few() : v_(42) { jump(); }
~Few();
private:
std::vector<int> v_;
void jump();
};
如果有人不了解这一点,那可能是以下两种情况之一:
对于在本帖子中评论 RAII(资源获取即初始化)的人,这里有一个激励性的例子。
class StdioFile {
FILE* file_;
std::string mode_;
static FILE* fcheck(FILE* stream) {
if (!stream)
throw std::runtime_error("Cannot open file");
return stream;
}
FILE* fdup() const {
int dupfd(dup(fileno(file_)));
if (dupfd == -1)
throw std::runtime_error("Cannot dup file descriptor");
return fdopen(dupfd, mode_.c_str());
}
public:
StdioFile(char const* name, char const* mode)
: file_(fcheck(fopen(name, mode))), mode_(mode)
{
}
StdioFile(StdioFile const& rhs)
: file_(fcheck(rhs.fdup())), mode_(rhs.mode_)
{
}
~StdioFile()
{
fclose(file_);
}
StdioFile& operator=(StdioFile const& rhs) {
FILE* dupstr = fcheck(rhs.fdup());
if (fclose(file_) == EOF) {
fclose(dupstr); // XXX ignore failed close
throw std::runtime_error("Cannot close stream");
}
file_ = dupstr;
return *this;
}
int
read(std::vector<char>& buffer)
{
int result(fread(&buffer[0], 1, buffer.size(), file_));
if (ferror(file_))
throw std::runtime_error(strerror(errno));
return result;
}
int
write(std::vector<char> const& buffer)
{
int result(fwrite(&buffer[0], 1, buffer.size(), file_));
if (ferror(file_))
throw std::runtime_error(strerror(errno));
return result;
}
};
int
main(int argc, char** argv)
{
StdioFile file(argv[1], "r");
std::vector<char> buffer(1024);
while (int hasRead = file.read(buffer)) {
// process hasRead bytes, then shift them off the buffer
}
}
这里,当StdioFile
实例被创建时,资源(在此情况下为文件流)会被获取;当它被销毁时,资源会被释放。不需要try
或finally
块;如果读取导致异常,则fclose
会自动调用,因为它在析构函数中。
无论是正常离开main
还是通过异常离开,都保证会调用析构函数。在这种情况下,文件流会被清理。世界再次变得安全。 :-D
with
与C#的using
类似:你必须记得使用它,否则会出现泄漏。C++的RAII默认情况下是被使用的,你必须做一些特殊的事情来关闭它(例如,将一个对象new
成一个裸指针(这在现代C++中是不可取的),并且泄漏)。 - C. K. YoungRAII。
它始于构造函数和析构函数,但它不仅仅是这些。
它的全部意义在于在异常存在的情况下安全地控制资源。
RAII比finally等机制更加优越的原因在于它使代码更加安全易用,因为它将使用对象的责任从对象的用户转移到了对象的设计者。
使用RAII正确地使用StdioFile的示例。
void someFunc()
{
StdioFile file("Plop","r");
// use file
}
// File closed automatically even if this function exits via an exception.
void someFunc()
{
// Assuming Java Like syntax;
StdioFile file = new StdioFile("Plop","r");
try
{
// use file
}
finally
{
// close file.
file.close(); //
// Using the finaliser is not enough as we can not garantee when
// it will be called.
}
}
一个对@Pierre's answer的修改:
在Python中:
with open("foo.txt", "w") as f:
f.write("abc")
f.close()
会自动调用,无论是否发生异常。
通常可以使用contextlib.closing来完成,文档中如下描述:
closing(thing)
: return a context manager that closes thing upon completion of the block. This is basically equivalent to:
from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close()
And lets you write code like this:
from __future__ import with_statement # required for python version < 2.6 from contextlib import closing import urllib with closing(urllib.urlopen('http://www.python.org')) as page: for line in page: print line
without needing to explicitly close page. Even if an error occurs, page.close() will be called when the with block is exited.
Common Lisp具有RAII:
(with-open-file (stream "file.ext" :direction :input)
(do-something-with-stream stream))
首先,我很惊讶RAII不是更为人所知!我原以为至少对C++程序员来说RAII是显而易见的。然而现在我可以理解为什么人们实际上会问这个问题。我周围,包括我自己,都是C++狂热者...
所以我的秘密...我想那就是,我过去总是阅读Meyers、Sutter [编辑:]和Andrei,直到我完全掌握了它。