C++中的智能指针和RAII机制


一、RAII机制

RAII(Resource Acquisition is Initialization)是由C++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化

资源的使用一般需要经历获取、使用和销毁三个步骤,其中资源的销毁往往是程序员容易遗忘的一个环节,所以我们就需要一种让资源自动销毁的方法。C++之父给出的解决方案就是RAII机制,它充分利用了C++语言局部对象自动销毁的特性来控制资源的生命周期。

下面是一个简单的例子:

#include <iostream>

using namespace std;

class Resource {
    
    
public:
    Resource() {
    
    
        cout << "Resource()" << endl;
    };

    void useResource() {
    
    
        cout << "UseResource()" << endl;
    }

    virtual ~Resource() {
    
    
        cout << "~Resource()" << endl;
    }
};

class ResourceRAII {
    
    
private:
    Resource *m_resource;

public:
    ResourceRAII() {
    
    
        m_resource = new Resource();
    }

    Resource *getResource() {
    
    
        return m_resource;
    }

    ~ResourceRAII() {
    
    
        delete m_resource;
    }
};

int main() {
    
    
    ResourceRAII resourceRAII;
    auto resource = resourceRAII.getResource();
    resource->useResource();
}

此例中,对于资源类Resource的使用,我们只需要显示地获取和使用即可,在ResourceRAII生命周期结束后会自动将Resource释放,而不需要我们显示调用delete方法来析构Resource。


二、智能指针

智能指针就是一个RAII机制的典型应用。

智能指针是行为类似于指针的类对象,主要包括auto_ptr、unique_ptr和shared_ptr。智能指针的思想就是给指针一个可以释放其指向内存的析构函数,这样不管函数正常还是异常终止,指针及其指向的内存都能被正确回收。

#include <iostream>
#include <memory>

class A {
    
    
private:
    std::string m_name;
public:
    A(std::string name) : m_name(name) {
    
    }
    ~A() {
    
     std::cout << "~" + m_name << std::endl; }
};

int main() {
    
    
    {
    
    
        A *normal_p = new A{
    
    "normal_ptr"}; // 存在内存泄漏
    }
    {
    
    
        std::auto_ptr<A> auto_p (new A{
    
    "auto_ptr"});
    }
    {
    
    
        std::unique_ptr<A> auto_p (new A{
    
    "unique_ptr"});
    }
    {
    
    
        std::shared_ptr<A> auto_p (new A{
    
    "shared_ptr"});
    }
    return 0;
}
atreus@MacBook-Pro % clang++ -w main.cpp -o main -std=c++11
atreus@MacBook-Pro % ./main 
~auto_ptr
~unique_ptr
~shared_ptr
atreus@MacBook-Pro % 

尽管auto_ptr、unique_ptr、shared_ptr均为智能指针模板类,但相互之间也有区别:

  1. 对于auto_ptr,一个显而易见的问题就是如果将一个auto_ptr对象赋值给另一个auto_ptr对象,那么二者将会指向同一块内存,这将导致同一块内存释放两次。
  2. 为了解决这个问题,shared_ptr会跟踪引用特定对象的智能指针数,这被称为引用计数(类似于操作系统中的软链接),仅当最后一个shared_ptr过期时才会调用delete释放内存。
  3. 而unique_ptr则通过建立所有权的概念来应对这个问题,对于特定的对象,只能有一个unique_ptr拥有它,也只有拥有对象的unique_ptr才能删除该对象。unique_ptr通过赋值操作转让所有权,但转移过程中的源unique_ptr必须为临时对象,如果源unique_ptr在转移后还会存在一段时间,编译器将禁止这种转移操作以避免对无效数据的访问。此外,unique_ptr还有一个可用于数组的变体,因此在使用new []分配内存时,只能使用unique_ptr。
#include <memory>

int main() {
    
    
    std::unique_ptr<int> p(new int(1));
    std::unique_ptr<int> p_;

    p_ = p; // not allowed because of error: object of type 'std::unique_ptr<int>' cannot be assigned because its copy assignment operator is implicitly deleted
    p_ = std::unique_ptr<int>(new int(1)); // allowed

    return 0;
}

三、unique_ptr

unique_ptr不共享它所指向的内容,它本身也无法复制到其他unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何STL算法,只能移动unique_ptr。这意味着在移动unique_ptr时内存资源所有权将转移到另一unique_ptr,并且原始unique_ptr将不再拥有此资源

unique_ptr实现了独享所有权的语义。一个非空的unique_ptr总是拥有它所指向的资源,拷贝一个unique_ptr将不被允许,因为如果你拷贝了一个unique_ptr,那么拷贝结束后这两个unique_ptr都会指向相同的资源,它们都认为自己拥有这块资源,所以都会企图释放。因此unique_ptr是一个仅能移动(move only)的类型,当指针析构时,它所拥有的资源也被销毁。默认情况下,资源的析构是伴随着调用unique_ptr内部的原始指针的delete操作。

要想创建一个unique_ptr,我们需要将一个new操作符返回的指针传递给unique_ptr的构造函数,或者通过std::move来实现。总之,unique_ptr没有拷贝构造函数,不支持普通的拷贝和赋值操作。

#include <memory>
#include <iostream>

using namespace std;

int main() {
    
    
    unique_ptr<int> p1(new int(1521));
    cout << *p1 << endl;

    unique_ptr<int> p2(p1); // error: call to implicitly-deleted copy constructor of 'unique_ptr<int>'
    unique_ptr<int> p2 = p1; // error: call to implicitly-deleted copy constructor of 'unique_ptr<int>'
    unique_ptr<int> p2 = move(p1);
    cout << *p2 << endl;

    unique_ptr<int> p3(move(p2));
    cout << *p3 << endl;
}

标准库还提供了一个可以管理动态数组的unique_ptr版本unique_ptr<类型[]>

#include <memory>
#include <iostream>

using namespace std;

int main() {
    
    
    unique_ptr<int[]> parr(new int[3] {
    
    1, 2, 3});
    cout << parr[0] << " " << parr[1] << " " << parr[2] << " " << endl;
}

此外,除了使用std::move转让所有权,从函数中返回时,所有权会自动从一个局部变量中转让到调用函数中,因此可以通过函数返回unique_ptr。

#include <memory>
#include <iostream>

using namespace std;

unique_ptr<int> clone(int x)
{
    
    
    unique_ptr<int> p(new int(x));
    return p; // 返回unique_ptr
}

int main() {
    
    
    unique_ptr<int> ret = clone(1521);
    cout << *ret << endl;
}

unique_ptr大多数情况下都需要与std::move配合使用,从而实现所有权的转让。

#include <memory>
#include <iostream>

using namespace std;

void out(unique_ptr<int> x) {
    
    
    cout << *x << endl;
}

int main() {
    
    
    unique_ptr<int> p(new int(1521));
    out(move(p));
}


参考:

https://zhuanlan.zhihu.com/p/34660259
https://www.cnblogs.com/DswCnblog/p/5628195.html

猜你喜欢

转载自blog.csdn.net/qq_43686863/article/details/130028930