了解术语和概念的含义
您能否让C ++开发人员详细描述RAII是什么,为什么它很重要,以及它是否与其他语言有任何关联?
我确实知道一点。 我相信它代表“资源获取是初始化”。 然而,这个名字并不与我对RAII的理解(可能是不正确的)有所联系:我认为RAII是一种在堆栈上初始化对象的方式,以便当这些变量超出范围时,析构函数会自动被称为导致资源被清理。
那么,为什么不叫“使用栈来触发清理”(UTSTTC :)呢? 你如何从那里到达“RAII”?
你怎么能在堆栈上做些什么来清理堆上的东西呢? 另外,有没有你不能使用RAII的情况? 你有没有发现自己希望收集垃圾? 至少有一个垃圾收集器可以用于某些对象,同时让其他人被管理?
谢谢。
那么,为什么不叫“使用栈来触发清理”(UTSTTC :)呢?
RAII告诉你该怎么做:在构造函数中获取你的资源! 我会补充:一个资源,一个构造函数。 UTSTTC只是其中的一个应用,RAII更多。
资源管理很糟糕。 在这里,资源就是使用后需要清理的任何东西。 对许多平台上的项目进行研究表明,大多数错误都与资源管理有关 - 在Windows上尤其糟糕(由于对象和分配器的类型众多)。
在C ++中,由于异常和(C ++样式)模板的组合,资源管理特别复杂。 有关引擎盖下的窥视,请参阅GOTW8)。
C ++保证当且仅当构造函数成功时调用析构函数。 依靠这一点,RAII可以解决一般程序员可能不知道的许多令人讨厌的问题。 这里有一些例外,“我的局部变量在我返回时会被销毁”。
让我们从使用RAII的过于简单化的FileHandle
类开始:
class FileHandle
{
FILE* file;
public:
explicit FileHandle(const char* name)
{
file = fopen(name);
if (!file)
{
throw "MAYDAY! MAYDAY";
}
}
~FileHandle()
{
// The only reason we are checking the file pointer for validity
// is because it might have been moved (see below).
// It is NOT needed to check against a failed constructor,
// because the destructor is NEVER executed when the constructor fails!
if (file)
{
fclose(file);
}
}
// The following technicalities can be skipped on the first read.
// They are not crucial to understanding the basic idea of RAII.
// However, if you plan to implement your own RAII classes,
// it is absolutely essential that you read on :)
// It does not make sense to copy a file handle,
// hence we disallow the otherwise implicitly generated copy operations.
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// The following operations enable transfer of ownership
// and require compiler support for rvalue references, a C++0x feature.
// Essentially, a resource is "moved" from one object to another.
FileHandle(FileHandle&& that)
{
file = that.file;
that.file = 0;
}
FileHandle& operator=(FileHandle&& that)
{
file = that.file;
that.file = 0;
return *this;
}
}
如果构造失败(例外),则不会调用其他成员函数 - 甚至不是析构函数。
RAII避免使用处于无效状态的对象。 在我们使用这个物体之前它已经让生活变得更轻松了。
现在,让我们看看临时对象:
void CopyFileData(FileHandle source, FileHandle dest);
void Foo()
{
CopyFileData(FileHandle("C:source"), FileHandle("C:dest"));
}
有三种错误情况需要处理:无法打开文件,只能打开一个文件,可以打开两个文件,但复制文件失败。 在非RAII实施中, Foo
必须明确处理所有三种情况。
即使在一个声明中获取多个资源,RAII也会释放所获取的资源。
现在,让我们汇总一些对象:
class Logger
{
FileHandle original, duplex; // this logger can write to two files at once!
public:
Logger(const char* filename1, const char* filename2)
: original(filename1), duplex(filename2)
{
if (!filewrite_duplex(original, duplex, "New Session"))
throw "Ugh damn!";
}
}
的构造Logger
会失败,如果original
的构造失败(因为filename1
无法打开), duplex
的构造失败(因为filename2
无法打开),或里面写文件Logger
的构造体失败。 在任何这些情况下, Logger
的析构函数都不会被调用 - 所以我们不能依赖Logger
的析构函数来释放这些文件。 但是如果构建了original
则在清理Logger
构造函数期间将调用其析构函数。
RAII简化了局部施工后的清理工作。
负面点:
消极点? 所有问题都可以通过RAII和智能指针来解决;-)
当你需要延迟采集时,RAII有时会很笨拙,将聚集的物体推到堆上。
想象一下,Logger需要一个SetTargetFile(const char* target)
。 在这种情况下,仍然需要成为Logger
成员的句柄需要驻留在堆上(例如,在智能指针中,以适当地触发句柄的销毁)。
我从来不希望垃圾收集真的。 当我做C#的时候,我有时会感到一阵幸福,我不需要关心,但是我更想念所有可以通过确定性破坏创造出来的酷玩具。 (使用IDisposable
只是不会削减它。)
我有一个特别复杂的结构,可能从GC中受益,其中“简单”智能指针会导致多个类的循环引用。 我们通过仔细平衡强弱指针来混淆视听,但任何时候我们想要改变某些事情,我们都必须研究一个关系图。 GC可能会更好,但某些组件拥有应尽快发布的资源。
关于FileHandle示例的说明:它不是完整的,只是一个示例 - 但结果不正确。 感谢Johannes Schaub指出并将FredOverflow转化为正确的C ++ 0x解决方案。 随着时间的推移,我已经解决了这里记录的方法。
那里有优秀的答案,所以我只是添加一些被遗忘的东西。
0. RAII是关于范围的
RAII是关于两者:
其他人已经回答了这个问题,所以我不会详细说明。
1.在Java或C#中编写代码时,您已经使用RAII ...
MONSIEUR JOURDAIN:什么! 当我说,“妮可,给我穿上我的拖鞋,给我睡前用具,”那是散文吗?
哲学大师:是的,先生。
MONSIEUR JOURDAIN:四十多年来,我一直在讲散文,却对此毫不知情,我非常感谢你教给我。
- Molière:中产阶级绅士,第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进行任何操作。 例如,在C ++中没有直接内置的等价synchronized
或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
缺乏?
[SHOUTING]这是最后的倒计时!
- 欧洲:最后的倒计时(对不起,我没有引号,这里... :-)
finally
子句在C#/ Java中用于在范围退出情况下处理资源处置(通过return
或抛出的异常)。
精明的规范读者会注意到C ++没有最后的条款。 这不是一个错误,因为C ++不需要它,因为RAII已经处理资源处置。 (并且相信我,编写一个C ++析构函数比编写正确的Java finally子句更容易,甚至是C#的正确Dispose方法)。
不过,有时候, finally
条款会很酷。 我们可以用C ++来做到吗? 我们可以! 再次使用RAII。
结论:RAII不仅仅是C ++中的哲学:它是C ++
RAII? 这是C ++!
- C ++开发者的愤怒评论,被无名的斯巴达国王和他的300个朋友无耻复制
当你达到C ++的某种级别的经验时,你开始考虑RAII,就构造者和析构者自动执行而言。
你开始考虑范围,并且{
和}
字符成为代码中最重要的字符。
几乎所有的RAII都适合:异常安全,互斥锁,数据库连接,数据库请求,服务器连接,时钟,操作系统句柄等,以及最后但并非最不重要的内存。
数据库部分不可忽略,因为如果您接受支付价格,您甚至可以使用“事务编程”风格编写代码,执行代码行和行,直到最终决定是否要提交所有更改,或者,如果不可能,则将所有更改恢复(只要每条线至少满足强力例外保证)。 (请参阅Herb's Sutter文章的第二部分,了解事务性编程)。
就像一个谜题一样,一切都合适。
RAII是C ++的重要组成部分,如果没有它,C ++就无法成为C ++。
这就解释了为什么有经验的C ++开发人员对RAII感到如此迷恋,为什么RAII是他们尝试使用其他语言时首先要搜索的内容。
它解释了为什么垃圾收集器虽然是一个巨大的技术本身,但从C ++开发人员的角度来看并不令人印象深刻:
请参见:
除C ++外,其他语言的程序员是否使用,了解或理解RAII?
RAII和C ++中的智能指针
C ++是否支持“终于”阻止? (我听说过什么'RAII'?)
RAII与例外
等等..
链接地址: http://www.djcxy.com/p/72979.html