C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++智能指针

C++智能指针详解

作者:顽張先生

从比较简单的层面来看,智能指针是RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制对普通指针进行的一层封装。这样使得智能指针的行为动作像一个指针,本质上却是一个对象,这样可以方便管理一个对象的生命周期

优缺点:

一. unique_ptr独占指针

特点

都是围绕独占展开

特点一: 如其名,独占。也就是说同一个内存空间同时只能有一个指针来管理。

int* pi = new int(10); //利用传统指针在堆区开辟一个存放整数的区域
std::unique_ptr<int> u_pi_01{pi};//通过传统指针创建智能指针
std::unique_ptr<int> u_pi_02{pi};//有意让两个独占指针同时指向同一个内存区域

这么写编译器不会报错,但运行时会提示 error:double free detected in tcache 2

这也就印证了第一个特点,一个内存区域只能由一个指针管理。

特点二: 当指针超出作用域时,内存自动释放

//由于指针的本质也是变量,离开作用范围就会自动释放
//因此我们需要通过在外部创建变量来保存指针所保存的地址
int* external_pi_1;//用于存储传统指针的地址
int* external_pi_2;//用于存储智能指针的地址
 {
    int *pi = new int(10);//使用传统指针在堆区开辟内存存储整形
    external_pi_1 = pi;   
    std::unique_ptr<int> u_pi{new int(10)};//使用智能指针在堆区开辟内存存储
    external_pi_2 = u_pi.get();
    std::cout << *external_pi_1 << std::endl;//输出10
    std::cout << *external_pi_2 << std::endl;//输出10
 }
std::cout << *external_pi_1 << std::endl;//输出10
std::cout << *external_pi_2 << std::endl;//输出0

可见传统指针在局部作用域中开辟的内存在外部同样可以访问,也就是说我们使用传统指针开辟内存之后在离开作用域时需要加上释放内存的操作,不然会造成内存泄漏。

而智能指针我们不需要手动释放内存,在离开作用域后会自动释放。

特点三:由于特点一,修改指针不可以copy ,只能Move(转移归属权)

std::unique_ptr<int> u_pi1 = std::make_unique<int>(10);
//std::unique_ptr<int> u_pi2 = u_pi1;//尝试用拷贝的方式共享内存,error:可别忘了这是独占指针
std::unique_ptr<int> u_pi2 = move(u_pi1);使用move方法转移内存拥有权。

也就是说,通过move函数,把指针u_pi1所指内存中的值掏空,然后安到指针u_pi2所指的内存上。

创建方式

方式一: 通过已有的传统指针创建

int* pi = new int(10); //使用传统指针在堆区开辟一个空间
std::unique_ptr<int> u_pi{pi};//利用创通指针创建智能指针

方式二: 通过new方法创建

std::unique_ptr<int> u_pi{new int(10)};

方式三: 通过std :: make_unique创建

std::unipue_ptr<int> u_pi = std::make_unique<int>(10);

传递方式

方式一: 通过move(),转移拥有权.

void show(std::unique_ptr<int> u_pi)
{
    std::cout<<*u_pi<<std::endl;
}
void test()
{
    std::unique_ptr<int> u_pi{new int(10)};
    show(move(u_pi)); //通过move转移拥有权
}

注意:将指针的拥有权转入函数中后,在原作用域指针将被释放,而该指针将在函数调用结束时释放。也就是说,将智能指针以move的形式传入函数后,在原作用域不能再使用该指针。

方式二: 通过引用传递

void show(const std::unique_ptr<int> &u_pi)//加cosnt 不是不能改变指向的值,不能改变指针的指向
{
    std::cout << *u_pi << std::endl;
    //u_pi.reset();加了const所以不能清空
}
void test()
{
    std::unique_ptr<int> u_pi{new int(10)};
    show(u_pi);
}

注意: 将指针以引用的方式传入函数,那么该指针在原作用域依然存活,并可以和所调用函数共同操作该内存空间数据。

方式三: 链式传递

std::unique_ptr<Person> get_unique(std::string str)
{
    std::unique_ptr<Person> u_pi{new Person(str)};
    return u_pi;
}
void test()
{
    get_unique("hua")->show();//链式
}

简单使用

class Person
{
public:
    Perosn(std::string name):m_name(name){};
    void show()
    {
        std::cout<<"name is "<<m_name<<std::endl;
    }
private:
    std::string m_name;   
};
int main()
{
std::unique_ptr<Person> u_p{new Person("kimi")}; //用自定义类型创建
u_p->show();//可以通过->调用函数
(*u_p).show();//通过*解引用
std::cout<<u_p.get()<<std::endl;//通过get()获取地址
u_p.reset();//清空指针
return 0;
}

隐藏危险

用已有指针创建时,没有及时清空传统指针,导致同时有两个指针指向这块已经被“独占”的区域。

int* pi = new int(10);
std::unique_ptr<int> u_pi{pi};//使用传统指针创建,上式开辟的区域被独占
*pi = 20; //没有及时清空,依然可以通过独占指针以外的方式修改内存

二. shared_ptr 计数指针

特点

特点一: 可以通过copy共享内存。

std::shared_ptr<int> u_pi_1{new int(10)};
std::shared_ptr<int> u_pi_2 = u_pi_1;//通过复制拷贝

特点二: 通过use_count();来查看计数 ,copy 计数加一,销毁计数减一。

std::shanred_ptr<int> s_pi{new int(10)}; //s_pi.use_count() == 1
std::shanred_ptr<int> s_pi_copy = s_pi;  //s_pi.use_count() == 2
s_pi = nullptr;//清空指针                 //s_pi_copy.use_count() == 1

特点三: 无论多少指针,都同用一份数据,因而同一份数据的use_count()一致。

std::shared_ptr<int> s_pi{new int(10)}; //s_pi.use_count() == 1
std::shared_ptr<int> s_pi_2 = s_pi;     //s_pi.use_count() == 2
std::shared_ptr<int> s_pi_3 = s_pi_2;   //s_pi.use_count() == 3
s_pi_2 = bullptr; //清空2指针            //s_pi.use_count() == 2

传递方式

void get_use_1(std::shared_ptr<int> s_pi)
{
    std::cout << s_pi.use_count() << std::endl;
}
void get_use_2(std::shared_ptr<int>& s_pi)
{
    std::cout << s_pi.use_count() << std::endl;
}
void test()
{
    std::shared_ptr<int> s_pi{new int(10)};
    std::cout << s_pi.use_count() << std::endl;
    get_use_1(s_pi);//在函数中计数会增加,但随着函数销毁,计数复原
    get_use_2(s_pi);//以引用方式传入,指针还是那个指针,计数不会增加
}

输出:1 2 1

隐藏危险

share_ptr带来的循环依赖问题

class Person
{
public:
    void set_friend(share_ptr<Person> p)
 
    _friend = p;
private:
    share_ptr<Person> _frient;
};
int main()
{
   share_ptr<Person> p1 = make_shared("P1");
   share_ptr<Person> p2 = make_shared("P2");
    p1->set_friend(P2);
    p2->set_friend(P1);//造成循环依赖,在main中的话,不会执行析构
}

解决:将_friend属性改为weak_ptr 。

三. weak_ptr

weak_ptr 是一个不需要所有权的指针,所以我们可以通过用weak_ptr来声明属性,解决循环依赖

class Person
{
public:
    void set_friend(share_ptr<Person> p)
    _friend = p;
private:
    weak_ptr<Person> _frient;//使用weak_ptr解决循环依赖
};
int main()
{
    share_ptr<Person> p1 = make_shared("P1");
    share_ptr<Person> p2 = make_shared("P2");
    p1->set_friend(P2);
    p2->set_friend(P1);
}

可以通过lock()来将weak_pte升级为shared_ptr;

std::weak_ptr<Person> w_pi{new Person("hua")};
std::shared_ptr <Person> s_pi2 = w_pi.lock();

到此这篇关于C++智能指针详解的文章就介绍到这了,更多相关C++智能指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文