C++智能指针

文章目录

  • 基础概念
  • 使用
    • std::shared_ptr
      • std::shared_ptr进阶
    • std::weak_ptr
    • std::unique_ptr
  • 使用小技巧

基础概念

首先智能指针的出现是为了应对多个指针指向同一块内存的情况,我们这里讲的只能指针分为2种分别是std::shared_ptr,std::unique_ptr

对于std::shared_ptr当我们多个智能指针指向一块内存的时候,他们可以同时操作这块区域,和普通指针没什么两样,但是当我们的所有智能指针(指向同一块内存)超出生命周期(超过范围scope)后,那么这块内存会自动的被销毁,防止出现内存泄漏

对于std::unique_ptr就完全不一样,一块内存区域只能被一个std::shared_ptr指,这个std::shared_ptr指针不能copy给别的对于std::shared_ptr变量,只能使用std::move()在使用std::move()的同时,原std::unique_ptr对于原来指向的内存区域的所有权被release了,当std::unique_ptr超出作用域,那么其指向的内存区域就自动销毁

使用

std::shared_ptr

#include 
#include 
#include 

class A{
public:
    A(){ std::cout << "construct!!!" << std::endl; };
    ~A(){ std::cout << "destruct!!!" << std::endl; };
};

void func(std::shared_ptr<A> impl){
    std::shared_ptr<A> impl2 = impl;
}

int main(){
    auto a = new A;
    std::shared_ptr<A> impl1_(a);
    func(impl1_);
    std::cout << "back to main" << std::endl;
    //std::shared_ptr impl2_(a);
}

在上述的简单代码中,main函数第二行代表我们用一个std::shared_ptr指向一块内存区域,再在函数func中我们用另一个std::shared_ptr指向同样的区域,在func返回(std::shared_ptr impl2)被销毁后,并没有触发析构函数,而是在std::shared_ptr impl1超出作用域的时候自动触发析构,
注意使用=号给shared_ptr赋值的时候只能传递shared_ptr对象,而非被shared_ptr指向的对象如下

 auto a = new A;
 std::shared_ptr<A> sp = a; //error!!!智能指针用等号赋值的时候智能传递智能指针对象,而不能传递智能指针指向的对象
 std::shared_ptr<A> sp(a); //yes!!!如果想传递被智能指针管理的对象,可以直接调用智能指针sp的构造函数

还有一个问题是我们如果先new一个对象,将这个对象赋值给shared_ptr,在shared_ptr对象生命周期结束前delete刚刚new的对象,最后等shared_ptr生命周期结束后会发生double delete

std::shared_ptr进阶

多个shared_ptr之间共享多个数据包含2个reference counter和其他数据

  • a counter for the std::shared_ptr.
  • a counter for the std::weak_ptr.
  • eventually further data like a special deleter or an allocator.

下图描述的很好

2个counter的作用不一样,第一个对于,当counter for the std::shared_ptr为0就会调用析构函数清除管理的obj,当counter for the std::weak_ptr.为0就会析构shared_pointer本身

看下面代码为何会报错

#include 
#include 
#include 

class A{
    int data;
public:
    A(){}
    A(int i ):data(i){}
    A(const A&& rhs){ std::cout << "move construct" << std::endl;}
    A(const A& rhs) { std::cout << "copy construct " << std::endl; }
};
-int main(){
    std::vector< std::shared_ptr<A> > v;
    v.push_back(new A(1));
    
    return 0;
}

因为push_back()的时候是=号赋值,而shared_ptr在使用=号赋值的时候只接收shared_ptr对象,不能接收被shared_ptr管理的对象,所以需要先将push_back()中push的对象换成shared_ptr,如何换成shared_ptr,使用make_shared<>()

#include 
#include 
#include 

class A{
    int data;
public:
    A(){std::cout << "common construct" << std::endl;}
    A(int i ):data(i){}
    A(const A&& rhs){ std::cout << "move construct" << std::endl;}
    A(const A& rhs) { std::cout << "copy construct " << std::endl; }
};


int main(){
    std::vector< std::shared_ptr<A> > v;
    v.push_back(std::make_shared<A>());
}

std::weak_ptr

先看下面的代码

#include 
#include 

struct B;
struct A {
  std::shared_ptr<B> b;  
  ~A() { std::cout << "~A()\n"; }
};

struct B {
  std::shared_ptr<A> a;
  ~B() { std::cout << "~B()\n"; }  
};

void useAnB() {
  auto a = std::make_shared<A>();
  auto b = std::make_shared<B>();
  a->b = b;
  b->a = a;
}

int main() {
   useAnB();
   std::cout << "Finished using A and B\n";
}

上面的程序除了程序结束,std::make_sharedstd::make_shared所创建的A obj和B obj永远不会被释放,因为上述的程序是一个经典的std::shared_ptr cyclic references

--- useAnB
A obj <----------------------------> B obj
↑			                           ↑
|									   |
|                                      |
|                                      |
|                                      |
shared_ptr a      				shared_ptr b

只想完函数useAnB后shared_ptr a中记录的对象A obj被引用了2次(一次是shared_ptr a还有一次是B obj),B obj同样他的reference count也是2,所以在程序结束前对象A obj和对象B obj之间相互引用的结不解开他们就不能被析构,遗憾的是我们用的std::make_shared,不能直接访问对象本身…

所以这里有个概念被提出来叫做std::weak_prt,也就是说weak指针没有关于他所指向对象的主权,不能通过weak_ptr去调用他所指向的对象,换句话说weak_ptr和shared_ptr一起使用的时候weak_ptr不会增加shared_ptr的reference count,还是上面代码我们用weak_ptr

#include 
#include 

struct B;
struct A {
  std::weak_ptr<B> b;  
  ~A() { std::cout << "~A()\n"; }
};

struct B {
  std::weak_ptr<A> a;
  ~B() { std::cout << "~B()\n"; }  
};

void useAnB() {
  auto a = std::make_shared<A>();
  auto b = std::make_shared<B>();
  a->b = b;
  b->a = a;
}

int main() {
   useAnB();
   std::cout << "Finished using A and B\n";
}

在执行完useAnB后对象B和对象A的reference count都是1,都是由函数useAnB中make_shared所指向的

std::unique_ptr

#include 
#include 

class A{
public:
    A(){ std::cout << "construct!!!" << std::endl; };
    ~A(){ std::cout << "destruct!!!" << std::endl; };
};

void func(std::shared_ptr<A> impl){
    std::shared_ptr<A> impl2 = impl;
}

int main(){
    std::unique_ptr<A> p1(new A);
    //std::unique_ptr p2 = p1; //error 因为一块内存区域new A只能被一个std::unique_ptr指针所指
    std::unique_ptr<A> p3 = std::move(p1);
}

我们直接看main函数第二行,假如release注释就会报错,因为一块内存区域new A只能被一个std::unique_ptr指针所指
main函数第三行右边的std::move()代表std::unique_ptrp1对原有的内存区域的所有权被release,并且所有权转移给unique_ptr
p3,换句话说此时的new A的这一块内存区域被p3这个智能指针所指向,而非之前的p1

使用小技巧

我一般在class中使用 std::shared_ptr去指向,private中的所有变量,且private中的所有变量都存放在一个struct中,代码如下

#include 
#include 
#include 


class A{
public:
    A();
    ~A();

private:
    struct impl;
    std::shared_ptr<struct impl> impl_;

};

struct A::impl{
    int id;
    std::string name;
};

A::A()
: impl_(new struct A::impl){

}

当我们的class对象作用域消失,那么对应的智能指针std::shared_ptr impl_也会随着消失,当智能指针小时其指向的内存区域也就是struct的内容也会消失,避免了我们忘记release class中的某些成员,假如这些成员占用内存很大,且是放于heap中,并且忘记在析构函数中remalloc或者delete,那么就会内存泄漏,如果用std::shared_ptr impl_将其"包起来",我们就可以避免这些烦恼

你可能感兴趣的:(c/c++,c++,开发语言)