一个简单的例子是一个RAII机制文件类。不用RAII的代码如下:
File file("/path/to/file"); // Do stuff with file
file.close();
换句话说,我们必须确保在完成文件后关闭文件。 这有两个缺点
- 首先,无论我们在哪里使用File,都必须调用File :: close()
- 如果我们忘记这么做的话,我们会比我们需要的时间更长。
第二个问题是如果在我们关闭文件之前抛出一个异常呢?
Java使用finally语句解决了第二个问题:
try {
File file = new File("/path/to/file"); // Do stuff with file
} finally {
file.close();
}
C ++使用RAII解决了这两个问题 - 也就是在File的析构函数中关闭文件。 只要File对象在正确的时间被销毁(它应该是无论如何),关闭文件是为我们搞定。 所以,我们的代码现在看起来像这样:
File file("/path/to/file");
// Do stuff with file
// No need to close it - destructor will do that for us
在Java中无法完成的原因是我们无法保证对象何时被销毁,所以不能保证何时会释放文件等资源。
在智能指针上 - 很多时候,我们只是在堆栈上创建对象。 例如:
这是可以的 - 但如果我们想要返回str呢? 我们可以写这个:
std::string foo() {
std::string str;
// Do cool things to or using str
return str;
}
返回类型是std :: string - 这意味着我们正在返回值。 这意味着我们复制str并实际返回副本。 这可能是昂贵的,我们可能想要避免复制它的成本。 因此,我们可能会想出通过引用或指针返回的想法。
std::string* foo() {
std::string str;
// Do cool things to or using str
return &str;
}
不幸的是,这段代码不起作用。 我们正在返回一个指向str的指针 - 但是str是在堆栈上创建的,所以我们一旦退出foo()就会被删除。 换句话说,当调用者获得指针的时候,这是无用的(并且可以说比使用它更糟糕,因为使用它会导致各种各样的时髦错误)
那么,解决方案是什么? 我们可以使用新的方法在堆上创建str - 这样,当foo()完成时,str将不会被销毁。
std::string* foo() {
std::string* str = new std::string();
// Do cool things to or using str
return str;
}
当然,这个解决方案也不完美。 原因是我们已经创建了str,但是我们从不删除它。 这在一个非常小的程序中可能不是问题,但总的来说,我们要确保将其删除。 我们可以说,调用者在完成对象之后必须删除对象。 缺点是调用者必须管理内存,这增加了额外的复杂性,并且可能导致错误,导致内存泄漏,即使不再需要删除对象。
这是智能指针进来的地方。下面的例子使用shared_ptr - 我建议你看看不同类型的智能指针来学习你真正想要使用的。
shared_ptr<std::string> foo() {
shared_ptr<std::string> str = new std::string();
// Do cool things to or using str
return str;
}
现在,shared_ptr会计算str的引用数量。 例
shared_ptr<std::string> str = foo();
shared_ptr<std::string> str2 = str;
现在有两个引用相同的字符串。 一旦没有剩余的str的引用,它将被删除。 因此,不必再担心自己删除它。
这个例子并不完美(至少!)两个原因。 首先,由于字符串的实现,复制一个字符串往往是廉价的。 其次,由于所谓的返回值优化,因为编译器可以做一些巧妙的事情来加快速度,所以按值返回可能并不昂贵。
所以,使用File类来尝试一个不同的例子。
假设我们想用文件作为日志。 这意味着我们想要以附加模式打开我们的文件:
File file("/path/to/file", File::append);
// The exact semantics of this aren't really important,
// just that we've got a file to be used as a log
现在,我们将我们的文件设置为其他几个对象的日志:
void setLog(const Foo & foo, const Bar & bar) {
File file("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
不幸的是,这个例子结束了可怕的 - 一旦这个方法结束,文件将被关闭,这意味着foo和bar现在有一个无效的日志文件。 我们可以在堆上构建文件,并将指向文件的指针传递给foo和bar:
void setLog(const Foo & foo, const Bar & bar) {
File* file = new File("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
但是,谁负责删除文件? 如果既不删除文件,也有内存和资源泄漏。 我们不知道foo或bar是否会先用文件结束,所以我们不能期望自己删除文件。 例如,如果foo在bar完成之前删除文件,bar现在有一个无效的指针。
所以,正如你可能已经猜到的那样,我们可以使用智能指针来帮助我们。
void setLog(const Foo & foo, const Bar & bar) {
shared_ptr<File> file = new File("/path/to/file", File::append);
foo.setLogFile(file);
bar.setLogFile(file);
}
现在,没有人需要担心删除文件 - 一旦foo和bar都完成,不再有任何文件的引用(可能是由于foo和bar被破坏),文件将被自动删除。
本文转自StackOverFlow