C++RAII机制与智能指针

一个简单的例子是一个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

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值