我注意到RAII已经越来越#2大量的关注,但在我的圈子(主要是C ++)RAII好像叫什么是类或析构函数那么明显了。
所以我如果那是因为我每天包围,由硬核的C ++程序员,真的很好奇和RAII刚刚是不是众所周知的一般(包括C ++),或者如果#2这一切的质疑是由于这样的事实我在与没有成长起来的C ++程序员,联系方式和其他语言的人,现在我只是不使用/知道RAII?
我注意到RAII已经越来越#2大量的关注,但在我的圈子(主要是C ++)RAII好像叫什么是类或析构函数那么明显了。
所以我如果那是因为我每天包围,由硬核的C ++程序员,真的很好奇和RAII刚刚是不是众所周知的一般(包括C ++),或者如果#2这一切的质疑是由于这样的事实我在与没有成长起来的C ++程序员,联系方式和其他语言的人,现在我只是不使用/知道RAII?
对于谁是关于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
有很多原因RAII是不是更好地了解原因。 首先,这个名字是不是特别明显。 如果我还不知道什么是RAII,我肯定不会从名字就猜出来了。 (资源采集是初始化?那是什么有析构函数或清理,这是真正刻画RAII做什么?)
另一个原因是,它并没有确定的清理在语言正常工作。
在C ++中,我们知道什么时候调用析构函数,我们知道在哪个析构函数被调用的顺序,我们可以定义他们做任何事情,我们喜欢。
在最现代的语言,一切都是垃圾收集,这使得RAII棘手实现。 没有理由为什么它是不可能的添加RAII的扩展,比方说,C#,但它并不那么明显,因为它是在C ++中。 但是,正如其他人所提到的,Perl和其他语言的支持RAII尽管被垃圾收集。
这就是说,它仍然可以创建在C#或其他语言自己的RAII风格的包装。 我这样做是在C#中前一阵子。 我写的东西,以确保数据库连接在使用后立即关闭,其中任何C ++程序员会看到作为RAII一个明显的候选人的任务。 当然,我们可以在包装的一切using
,每当我们使用一个数据库连接-statements,但是这只是杂乱,而且容易出错。
我的解决办法是写历时一个委托作为参数,然后调用时的辅助功能,打开的数据库连接,并且使用语句里面,它传递给委托功能,伪代码:
T RAIIWrapper<T>(Func<DbConnection, T> f){
using (var db = new DbConnection()){
return f(db);
}
}
还不如好的或明显为C ++ - RAII,但它实现了大致相同的事情。 每当我们需要的DbConnection,我们必须调用这保证了它会被关闭之后这个辅助功能。
我用C ++˚RAII所有的时间,但我也开发了VB6很长一段时间,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。
它以一个构造函数和析构函数,但它不止于此。
它是所有关于安全的异常的情况下控制资源。
是什么让RAII优于终于和这样的机制是,它使代码更安全的使用,因为它的动作责任正确地使用对象从对象的用户对象的设计师。
读这个
实施例使用StdioFile正确使用RAII。
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.
}
}
因为你必须明确地加入try {}最后{}块,这使得编码的这种方法更容易出错( 即它是一个需要考虑的例外对象的用户)。 通过使用RAII异常安全具有当对象被实施一次代码。
这个问题是这样的C ++具体。
答案很简单:第
更长的答案:
它需要构造器/解析器/异常和具有限定的寿命的对象。
那么技术上它不需要例外。 它只是变得更加有用,当异常可能被使用,因为它使得控制异常很容易存在的资源。
但是,在控制可以留下一个功能早期,而不是执行所有的代码的函数( 如提前返回都非常有用。这就是为什么在碳多重返回点是一个坏的代码味道,而在C ++多重返回点是不是一个代码味道(因为)我们可以清理使用RAII])。
在C ++中控制寿命通过堆栈变量或智能指针来实现的。 但是,这不是我们能有一个严格控制寿命的唯一时间。 例如Perl的对象不栈为基础,但有由于引用计数非常受控的寿命。
与RAII的问题是缩写。 它没有明显的相关性的概念。 这是什么都与堆栈分配呢? 这就是它归结为。 C ++让你在栈上分配的对象,并保证即使堆栈展开他们的析构函数调用的能力。 在那光,那RAII听起来就像是封装的一种有意义的方式? 不,我从来没有听说过RAII的,直到我几个星期前来到这里,我甚至有,当我读到有人贴过,他们绝不会雇用一个C ++程序员,谁知道不知道什么是RAII是笑很难。 当然这个概念是众所周知的最重要能力的专业C ++开发人员。 这只是缩写,是考虑不周的。
的修改@皮埃尔的答案 :
在Python:
with open("foo.txt", "w") as f:
f.write("abc")
f.close()
的异常是否提出或不被自动调用。
一般而言,可以做到用contextlib.closing ,从documenation:
closing(thing)
:返回该块完成后关闭的事情上下文管理。 这基本上等同于:from contextlib import contextmanager @contextmanager def closing(thing): try: yield thing finally: thing.close()
并让你写这样的代码:
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
而无需显式关闭页面。 即使发生了错误,当与块退出page.close()将被调用。
Common Lisp的有RAII:
(with-open-file (stream "file.ext" :direction :input)
(do-something-with-stream stream))
请参阅: http://www.psg.com/~dlamkins/sl/chapter09.html
首先我很惊讶它没有比较知名的! 我完全没想RAII是,至少,明显的C ++程序员。 但是现在我想我能理解为什么人们实际上询问此事。 我包围,和我自己必须是,C ++怪胎...
所以,我的秘密。我想这将是我用来读取迈尔斯,萨特[编辑:]所有的时间多年前,直到我只是grokked它和安德烈。
与RAII的事情是,它需要确定性结束的东西是在C ++ stackbased对象保证。 如C#和Java依赖垃圾回收的语言没有这个保障,因此有某种方式被“狂奔”。 在C#中,这是通过实现IDisposable接口完成,几乎相同的使用模式的作物,然后向上basicly这就是激励的“使用”的语句之一,它确保了处理,并很好地了解和使用。
所以basicly成语是存在的,只是没有一个奇特的名字。
RAII是C ++的方式,以确保清除过程中,无论在代码中会发生什么的代码块后执行:代码执行到最后正确或将引发异常。 一个已被引用的例子是自动关闭文件的处理后,看到答案在这里 。
在其他语言中使用其他机制来实现这一目标。
在Java中,你必须尝试{}终于{}构造:
try {
BufferedReader file = new BufferedReader(new FileReader("infilename"));
// do something with file
}
finally {
file.close();
}
在Ruby中,你有自动挡参数:
File.open("foo.txt") do | file |
# do something with file
end
在Lisp你有unwind-protect
和预定义with-XXX
(with-open-file (file "foo.txt")
;; do something with file
)
在流程必须dynamic-wind
和预定义with-XXXXX
:
(with-input-from-file "foo.txt"
(lambda ()
;; do something
)
在Python你有尝试终于
try
file = open("foo.txt")
# do something with file
finally:
file.close()
C ++的解决方案RAII是,因为它迫使你创建一个类的各种清理你必须做的相当笨拙。 这可能会迫使你写了很多小傻类。
RAII的其他例子是:
这有点绑知道什么时候你的析构函数会被调用,虽然对不对? 因此,这并不完全是语言无关的,因为这不是一个在许多GC'd语言给出。
我想了很多其他语言的(那些没有delete
,例如)不给程序员在对象生命周期完全一样的控制,所以必须有其他的手段来提供确定性的处理资源。 在C#,例如,使用using
与IDisposable
是常见的。
RAII是流行的C ++,因为它是为数不多的(唯一?)语言可以分配复杂的范围局部变量之一,但没有一个finally
条款。 C#,Java和Python和Ruby都有finally
或等效。 C具有不finally
,而且当变量下降超出范围不能执行的代码。
我有同事谁是硬核,“读规范” C ++类型。 他们中许多人知道RAII,但我从来没有真正听到它使用的场景之外。
CPython的(C编写的官方Python)支持,因为它的使用即时范围基于破坏引用计数对象(而不是回收垃圾时)的RAII。 不幸的是,Jython(在Java中的Python)和PyPy不支持这个非常有用的RAII成语和它打破了很多传统的Python代码。 因此,对于便携式蟒你必须处理所有的异常手动和Java一样。
RAII是特定于C ++。 C ++有堆栈分配的对象,非托管对象的寿命,和异常处理的必要组合。