RAII : Resource Acquisition Is Initialization(资源获取即初始化)
class DynamicArray {
private:
int* data; // 指向动态分配的数组的指针
public:
DynamicArray(int size) {
data = new int[size]; // 在构造函数中分配内存
}
~DynamicArray() {
delete[] data; // 在析构函数中释放内存
}
};
void someFunction() {
DynamicArray arr(10); // 在栈上创建DynamicArray对象,分配了一个长度为10的数组
// 在这里,arr可以被使用,不需要手动释放内存
} // 在这里,arr离开了作用域,析构函数会被调用,释放内存
可以看到,上述函数中的局部对象是有作用域的,也就是在 { } 内,但当其结束生命周期的时候,堆区的内存并不会释放,如果不人工进行释放,则会造成资源泄露。 而如果程序很复杂的时候,需要为所有的new 分配的内存delete掉,导致效率下降,更可怕的是,程序的可理解性和可维护性明显降低了,当操作增多时,处理资源释放的代码就会越来越多,越来越乱。
在这种情况下引入了RAII思想来解决这类问题,RAII的做法是使用一个对象(因为对象的生命周期结束时一定会执行析构函数,所以可以在这上面做文章,把资源与类对象的生命周期绑定),在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源。也就是在调用析构函数的时候,会自动释放其获取的资源。让局部变量和局部变量指向的内存空间的生命周期保持一致。
智能指针
在资源管理方面,智能指针(std::shared_ptr和std::unique_ptr)是RAII最具代表性的实现,使用了智能指针,可以实现自动的内存管理,再也不用担心忘记delete造成内存泄漏了。
C++ 标准模板库 STL(Standard Template Library)共提供了四种智能指针:
auto_ptr(已弃用,被unique_ptr代替)
unique_ptr
shared_ptr
weak_ptr
unique_ptr 拥有对持有对象的唯一所有权,不能被复制到另外一个unique_ptr ,所持有的对象只能通过转移语义将所有权转移到另外一个unique_ptr
std::unique_ptr<A> a1(new A());
std::unique_ptr<A> a2 = a1;//编译报错,不允许复制
std::unique_ptr<A> a3 = std::move(a1);//可以转移所有权,所有权转义后a1不再拥有任何指针
unique_ptr本身拥有的方法主要包括:
1、get() 获取其保存的原生指针,尽量不要使用
2、bool() 判断是否拥有指针
3、release() 释放所管理指针的所有权,返回原生指针。但并不销毁原生指针。
4、reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针
std::unique_ptr<A> a1(new A());
A *origin_a = a1.get();//尽量不要暴露原生指针
if(a1)
{
// a1 拥有指针
}
std::unique_ptr<A> a2(a1.release());//常见用法,转义拥有权
a2.reset(new A());//释放并销毁原有对象,持有一个新对象
a2.reset();//释放并销毁原有对象,等同于下面的写法
a2 = nullptr;//释放并销毁原有对象
shared_ptr强调的是共享所有权。也就是说多个shared_ptr可以拥有同一个原生指针的所有权。
shared_ptr 是通过引用计数的方式管理指针,当引用计数为 0 时会销毁拥有的原生对象。
shared_ptr本身拥有的方法主要包括:
1、get() 获取其保存的原生指针,尽量不要使用
2、bool() 判断是否拥有指针
3、reset() 释放并销毁原生指针。如果参数为一个新指针,将管理这个新指针
4、unique() 如果引用计数为 1,则返回 true,否则返回 false
5、use_count() 返回引用计数的大小
std::shared_ptr<A> a1(new A());
std::shared_ptr<A> a2 = a1;//编译正常,允许所有权的共享
A *origin_a = a1.get();//尽量不要暴露原生指针
if(a1)
{
// a1 拥有指针
}
if(a1.unique())
{
// 如果返回true,引用计数为1
}
long a1_use_count = a1.use_count();//引用计数数量 weak_ptr配合shared_ptr,由于shared_ptr是通过引用计数来管理原生指针的,那么最大的问题就是循环引用(比如 a 对象持有 b 对象,b 对象持有 a 对象),这样必然会导致内存泄露。而weak_ptr不会增加引用计数,因此将循环引用的一方修改为弱引用,可以避免内存泄露。
weak_ptr本身拥有的方法主要包括:
1、expired() 判断所指向的原生指针是否被释放,如果被释放了返回 true,否则返回 false
2、use_count() 返回原生指针的引用计数
3、lock() 返回 shared_ptr,如果原生指针没有被释放,则返回一个非空的 shared_ptr,否则返回一个空的 shared_ptr
4、reset() 将本身置空
std::shared_ptr<A> a1(new A());
std::weak_ptr<A> weak_a1 = a1;//不增加引用计数
if(weak_a1.expired())
{
//如果为true,weak_a1对应的原生指针已经被释放了
}
long a1_use_count = weak_a1.use_count();//引用计数数量
if(std::shared_ptr<A> shared_a = weak_a1.lock())
{
//此时可以通过shared_a进行原生指针的方法调用
}
weak_a1.reset();//将weak_a1置空
使用场景
这个对象在对象或方法内部使用时优先使用unique_ptr。
这个对象需要被多个 Class 同时使用的时候使用shared_ptr
shared_ptr 的实践
class B
{
private:
std::shared_ptr<A> a_;
public:
B(std::shared_ptr<A>& a): a_(a) {}
};
class C
{
private:
std::shared_ptr<A> a_;
public:
C(std::shared_ptr<A>& a): a_(a) {}
};
std::shared_ptr<B> b_;
std::shared_ptr<C> c_;
void test_A_B_C()
{
std::shared_ptr<A> a = std::make_shared<A>();//使用std::make_shared创建shared_ptr。
b_ = std::make_shared<B>(a);
c_ = std::make_shared<C>(a);
}
在上面的代码中需要注意,我们使用std::make_shared代替new的方式创建shared_ptr。
因为使用new的方式创建shared_ptr会导致出现两次内存申请,而std::make_shared在内部实现时只会申请一个内存。因此建议后续均使用std::make_shared。
如果A想要调用B和C的方法怎么办呢?可否在A中定义B和C的shared_ptr呢?答案是不可以,这样会产生循环引用,导致内存泄露。
此时就需要weak_ptr出场了。
class A
{
private:
std::weak_ptr<B> b_;
std::weak_ptr<C> c_;
public:
void do_something() {}
void set_B_C(const std::shared_ptr<B>& b, const std::shared_ptr<C>& c)
{
b_ = b;
c_ = c;
}
};
a->set_B_C(b_, c_);
如果想要在A内部将当前对象的指针共享给其他对象,需要怎么处理呢?
class D
{
private:
std::shared_ptr<A> a_;
public:
std::shared_ptr<A>& a): a_(a) {}
};
class A
{
//上述代码省略
public:
void new_D()
{
//错误方式,用this指针重新构造shared_ptr,将导致二次释放当前对象
std::shared_ptr<A> this_shared_ptr1(this);
std::unique_ptr<D> d1(new D(this_shared_ptr1));
}
};
如果采用this指针重新构造shared_ptr是肯定不行的,因为重新创建的shared_ptr与当前对象的shared_ptr没有关系,没有增加当前对象的引用计数。这将导致任何一个shared_ptr计数为 0 时提前释放了对象,后续操作这个释放的对象都会导致程序异常。
此时就需要引入shared_from_this。对象继承了enable_shared_from_this后,可以通过shared_from_this()获取当前对象的shared_ptr指针。
class A: public std::enable_shared_from_this<A>
{
//上述代码省略
public:
void new_D()
{
//错误方式,用this指针重新构造shared_ptr,将导致二次释放当前对象
std::shared_ptr<A> this_shared_ptr1(this);
std::unique_ptr<D> d1(new D(this_shared_ptr1));
//正确方式
std::shared_ptr<A> this_shared_ptr2 = shared_from_this();
std::unique_ptr<D> d2(new D(this_shared_ptr2));
}
};
注意:尽量不要混用智能指针和原生指针等一系列可能导致对象二次销毁的操作!
在状态管理方面,线程同步中使用std::unique_lock或std::lock_guard对互斥量std::mutex进行状态管理也是RAII的典型实现,通过这种方式,我们再也不用担心互斥量之间的代码出现异常而造成线程死锁。
标签:std,智能,a1,互斥,shared,unique,ptr,指针 From: https://blog.51cto.com/u_14882565/9234215