FILE*
). 软件工程中流行的观念是, 不具备扩展性, 经不起追加功能的东西注定会悲剧. 现在假如需要给这货增加个缓冲区, 也许这样是可以的
struct File {
File(char const* filename, int buffer_size)
: file(fopen(filename, "r"))
, buffer(new char[buffer_size])
{
if (NULL == file) {
throw std::runtime_error(std::string("fail to open ") + filename);
}
}
~File()
{
fclose(file);
delete[] buffer;
}
private:
FILE* file;
char* buffer;
/* other members */
};
buffer
分配失败时, 一般会抛出
std::bad_alloc
.
这个类型的破绽相当多, 稍不注意就有可能漏资源. 首先是刚刚提到的
buffer
分配失败抛异常, 那么假如这个时候
file
已经打开成功了, 它会被关闭么? 其次, 假设
buffer
成功分配, 但这时
file
打开失败, 那么
buffer
是否会被释放呢?
很不幸的, 两者的答案都是 否 . 还是那句话, 因为
File
的构造函数没有走完, 这时抛出异常, 那么析构函数不会被执行. 因此,
不要尝试构造控制多于一个资源的类型
. 而遇到这种需求, 应该拆分资源, 然后将这些单元类型进行聚合, 如
struct File {
explicit File(char const* filename)
: file(fopen(filename, "r"))
{
if (NULL == file) {
throw std::runtime_error(std::string("fail to open ") + filename);
}
}
~File()
{
fclose(file);
}
private:
FILE* file;
/* other members */
};
struct Buffer {
explicit Buffer(int buffer_size)
: buffer(new char[buffer_size])
{}
~Buffer()
{
delete[] buffer;
}
private:
char* buffer;
/* other members */
};
struct BufferedFile {
BufferedFile(char const* filename, int buffer_size)
: file(filename)
, buffer(buffer_size)
{}
File file;
Buffer buffer;
/* other members */
};
Buffer
类型又犯了 C++ 中的另一个忌, STL 里有
std::string
和
std::vector
呢, 这又造起了轮子哎.
这样写很明显地能解决:
file
先行构造, 打开失败的话, 自然
buffer
就没机会构造了. 而当
buffer
构造失败的时候会发生什么呢? 当然,
file
会被析构, 要不然 RAII 连这么简单的情形都对付不了, 那就坑爹坑到死了.
看待初始化列表里面那一堆成员
file
和
buffer
的构造, 其实可以想象成在
BufferedFile
构造函数开头处声明的栈中对象, 其中任何一个构造时抛出异常时, 之前已经完成构造的对象则会依次析构掉. 不过, 有一点跟普通的栈中对象不同的是, 在
BufferedFile
构造函数结束之后, 也就是上篇文章中提到的对象从不可用状态切换到可用状态这个时刻之后, 它们不会析构. 这一点也是很显然的, 它们此时作为成员, 将与持有它们的
BufferedFile
对象拥有相同的作用域. 这又是 RAII 为程序员提供的状态切换的透明性的另一个方面.