C++智能指针

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

C++智能指针

顽張先生   2022-09-28 我要评论

优缺点:

  • 智能指针由原始指针的封装,优点是可以自动分配内存,不用担心内存泄漏问题。
  • 用于解决独占/共享所有权指针的释放,传输等问题。
  • 但是没有原始指针方便。

一. 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();//链式
}

简单使用

  • 通过get()获取地址
  • 可以通过->调用成员函数
  • 可以通过*调用解引用
  • 通过reset()清空指针
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

传递方式

  • 本质不变,在函数调用中,因为本身支持复制操作,所以不用加move可以直接传递。
  • 并且在传递到函数中,use_count() 会增加,并在函数销毁时候还原。
  • 在函数中修改指向的值,在外部的指针指向的值也会改变。
  • 使用引用传递,则在传递到函数中时,计数不会增加。
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();

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们