C++基础之智能指针

发布于:2025-06-26 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、概念

堆内存对象需要手动使用delete销毁如果没有使用delete销毁就会造成内存泄漏

所以C++在ISO98标准引入了智能指针概念ISO11趋于完善

使用智能指针可以堆内存对象具有栈内存对象特点原理需要手动回收内内存对象套上一个栈内存的模板类对象即可

使用智能指针需要引入头文件#include<memory>

C++四种智能指针

● auto_ptr自动指针)C++ISO98 ,已废弃)

● unique_ptr唯一指针)C++ISO11)

● shared_ptr共享指针C++ISO11)

● weak_ptr虚指针)C++ISO11)

二、auto_ptr 

#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
    string s;
public:
    Test(string s):s(s)
    {
        cout<<s<<"构造函数"<<endl;
    }
    ~Test()
    {
        cout<<s<<"析构函数"<<endl;
    }
    void show()
    {
        cout<<s<<"对象执行"<<endl;
    }
};
int main()
{
    {
    //创建一个Test对象,t1指针指向对象
    Test *t1=new Test("A");
    //将对象t1交给ap1管理
    auto_ptr<Test>ap1(t1);

    //访问对象的成员函数
    ap1.get()->show();  //ap1.get() <=等价=> t1

    //释放智能指针ap1对对象t1的管理权,但不销毁对象
//    ap1.release();

    //释放智能指针ap1对对象t1的管理权,且销毁对象
//    ap1.reset();

    //创建新的对象t2,销毁原t1对象,并将ap1管理的对象换为t2
    Test *t2=new Test("B");
    ap1.reset(t2);

//    //以上两句代码可简写为
//    ap1.reset(new Test("B"));

        ap1.get()->show();

    cout<<"局部代码块执行结束"<<endl;
    }
    cout<<"程序运行结束"<<endl;
    return 0;
}

/*
A构造函数
A对象执行
B构造函数
A析构函数
B对象执行
局部代码块执行结束
B析构函数
程序运行结束
*/

由于成员变量存在指针类型,其拷贝构造函数和赋值运算符重载函数的使用会出现问题,与浅拷贝不同,auto_ptr的复制语义(拷贝构造函数、赋值运算符重载函数)会导致智能指针对对象控制权的转移(非程序员本意),这也是他被废弃的原因

#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
    string s;
public:
    Test(string s):s(s)
    {
        cout<<s<<"构造函数"<<endl;
    }
    ~Test()
    {
        cout<<s<<"析构函数"<<endl;
    }
    void show()
    {
        cout<<s<<"对象执行"<<endl;
    }
};
int main()
{
    {
    //创建一个Test对象,t1指针指向对象
    Test *t1=new Test("A");

    //将对象t1交给ap1管理
    auto_ptr<Test>ap1(t1);
    cout<<ap1.get()<<endl;

   //显示调用拷贝构造函数
    auto_ptr<Test>ap2(ap1);//对t1的管理权从ap1到了ap2
    cout<<ap1.get()<<" "<<ap2.get()<<endl;
    //隐式调用拷贝构造函数
    auto_ptr<Test>ap3=ap2;//对t1的管理权从ap2到了ap3
    cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<endl;
    //使用赋值运算符
    auto_ptr<Test>ap4;
    ap4=ap3; //对t1的管理权从ap3到了ap4
    cout<<ap1.get()<<" "<<ap2.get()<<" "<<ap3.get()<<" "<<ap4.get()<<endl;


    cout<<"局部代码块执行结束"<<endl;
    }
    cout<<"程序运行结束"<<endl;
    return 0;
}

/*
A构造函数
0x1162670
0 0x1162670
0 0 0x1162670
0 0 0 0x1162670
局部代码块执行结束
A析构函数
程序运行结束
*/

三、unique_ptr 

作为auto_ptr的改进,unique_ptr对其他持有资源对象具有唯一控制权不可以通过常规复制语法转移或者拷贝资源对象控制

#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
    string s;
public:
    Test(string s):s(s)
    {
        cout<<s<<"构造函数"<<endl;
    }
    ~Test()
    {
        cout<<s<<"析构函数"<<endl;
    }
    void show()
    {
        cout<<s<<"对象执行"<<endl;
    }
};
int main()
{
    {
    //创建一个Test对象,t1指针指向对象
    Test *t1=new Test("A");

    //将对象t1交给up1管理
    unique_ptr<Test>up1(t1);
    cout<<up1.get()<<endl;

   //显示调用拷贝构造函数
    unique_ptr<Test>up2(move(up1));
    cout<<up1.get()<<" "<<up2.get()<<endl;
    //隐式调用拷贝构造函数
    unique_ptr<Test>up3=move(up2);
    cout<<up1.get()<<" "<<up2.get()<<" "<<up3.get()<<endl;
    //使用赋值运算符
    unique_ptr<Test>up4;
    up4=move(up3);
    cout<<up1.get()<<" "<<up2.get()<<" "<<up3.get()<<" "<<up4.get()<<endl;


    cout<<"局部代码块执行结束"<<endl;
    }
    cout<<"程序运行结束"<<endl;
    return 0;
}

/*
A构造函数
0xed2670
0 0xed2670
0 0 0xed2670
0 0 0 0xed2670
局部代码块执行结束
A析构函数
程序运行结束
*/

 四、shared_ptr

 

每次多一个shared_ptr对资源进行管理引用计数+1每个指向该对象shared_ptr对象销毁时引用计数-1最后一个shared_ptr对象销毁时计数清零资源对象销毁
(即对于所有管理某对象A的 shared_ptr,这些 shared_ptr共享的资源有:引用计数、对象资源) 
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
    string s;
public:
    Test(string s):s(s)
    {
        cout<<s<<"构造函数"<<endl;
    }
    ~Test()
    {
        cout<<s<<"析构函数"<<endl;
    }
    void show()
    {
        cout<<s<<"对象执行"<<endl;
    }
};
int main()
{
    shared_ptr<Test>sp3;
    {

      shared_ptr<Test>sp1=make_shared<Test>("A");
      cout<<"引用计数:"<<sp1.use_count()<<endl;
      cout<<"-------------"<<endl;
      shared_ptr<Test>sp2(sp1);
      cout<<"引用计数:"<<sp2.use_count()<<endl;
      cout<<"引用计数:"<<sp1.use_count()<<endl;
          cout<<"-------------"<<endl;
      sp3=sp2;
      cout<<"引用计数:"<<sp3.use_count()<<endl;

      cout<<"局部代码块执行结束"<<endl;
    }

    //此时sp1、sp2都已销毁,所以引用计数减2
    cout<<"引用计数:"<<sp3.use_count()<<endl;

    cout<<"程序运行结束"<<endl;
    return 0;
}

/*
A构造函数
引用计数:1
-------------
引用计数:2
引用计数:2
-------------
引用计数:3
局部代码块执行结束
引用计数:1
程序运行结束
A析构函数

*/

 五、weak_ptr

weak_ptr一个不控制资源对象智能指针不会影响资源引用计数主要目的协助shared_ptr工作

通过weak_ptr构造函数参数传入一个持有资源对象shared_ptr或者weak_ptr对象即可创建weak_ptr与资源对象呈弱相关性,因此不支持get等函数直接操作资源对象.

建议weak_ptr调用lock函数之前,先检测引用计数是否大于0,或使用expired()函数检测是否可转换为shared_ptr.

  • 因为不会增加引用计数,所以作为观察者,观察对象是否被释放
  • 若shared_ptr失效,weak_ptr可以通过lock函数“转正”
  • shared_ptr在特殊场景下也会造成内存泄漏(循环引用导致智能指针内存泄漏的问题),可以使用weak_ptr来解决
#include <iostream>
#include <memory>
using namespace std;
class Test
{
private:
    string s;
public:
    Test(string s):s(s)
    {
        cout<<s<<"构造函数"<<endl;
    }
    ~Test()
    {
        cout<<s<<"析构函数"<<endl;
    }
    void show()
    {
        cout<<s<<"对象执行"<<endl;
    }
};
int main()
{
     weak_ptr<Test>wp3;
    {

      shared_ptr<Test>sp1=make_shared<Test>("A");

      weak_ptr<Test>wp1=sp1;
      weak_ptr<Test>wp2(wp1);

      cout<<sp1.use_count()<<endl;
      cout<<wp1.use_count()<<endl;
      cout<<wp2.use_count()<<endl;
      cout<<"-----------"<<endl;
 //weak_ptr可以通过lock函数“转正”
      shared_ptr<Test>sp2=wp1.lock();

      wp3=wp2;
      cout<<wp3.use_count()<<endl;

      cout<<"局部代码块执行结束"<<endl;
    }
    //判断能否转正
     cout<<wp3.use_count()<<endl;
     if(wp3.expired())
     {
         cout<<"weak_ptr无法转正"<<endl;
     }


    cout<<"程序运行结束"<<endl;
    return 0;
}

/*
A构造函数
1
1
1
-----------
2
局部代码块执行结束
A析构函数
0
weak_ptr无法转正
程序运行结束

*/

 六、其他

1.nullptr

在C++11使用nullptr代替NULL作为空指针表示方式C++可以用作空指针常量表示指针指向任何有效内存地址

 2.类型推导

2.1  auto

使用auto关键字可以推导类型C++11引入

#include <iostream>
#include <memory>
using namespace std;

int main()
{
   auto i1=10;//typeof(i1)-->整型

   auto i2=1.5;//typeof(i2)-->浮点型

   
   auto i3=new auto(10); //typeof(i3)-->int *
 //int* i3=new int(10);
   cout<<*i3<<" "<<i3<<endl;


   auto i4='a';//typeof(i4)-->char
   
   
    return 0;
}



2.2 decltypt

decltypt可以推导表达式类型需要注意decltypt只会分析表达式类型不会具体计算表达式

3.初始化列表

#include <iostream>
#include <array>
#include <vector>

using namespace std;

class Student
{
private:
    string name;
    int age;
public:
    Student(string name,int age):name(name),age(age)
    {
    }

    void show()
    {
        cout << name << " " << age << endl;
    }
};

int main()
{
    Student s = {"张胜男",18}; // 列表初始化
    s.show();

    array<int,5>arr1 = {1,3,4,5,6};

    vector<int> vec1 ={1,2,3};

    int arr2[] = {2,3,45,6};

    int a{};
    cout << a << endl;  // 0
    return 0;
}

4.面试题

【面试题】C++11学过那些特性
  • auto(类型推导)
  • 智能指针(unique_ptr、 shared_ptr、weak_ptr)
  • 初始化列表
  • for-each
  • 类型转换函数
  • 继承构造
  • array
  • override

 5.  进制输出

C++11可以对整数进行不同进制输出
#include <iostream>

using namespace std;

int main()
{
    // 为了区分不同进制,可以增加进制显式的功能,此功能设定持久
    cout << showbase;

    cout << dec << 1234 << endl;    // 1234
    cout << oct << 1234 << endl;    // 02322

    // 输出进制是持久的
    cout << 9 << endl;  // 011

    cout << hex << 256 << endl; // 0x100

    // 取消进制显式的功能
    cout << noshowbase;
    cout << 16 << endl; // 10
    return 0;
}

6.  设定输出域宽度

可以使用setw()制定一个整数或者一个字符串输出占用与宽度

● 设定宽度小于数据本身时仍然显示为数据本身宽度

● 设定域宽度大于数据本身显式未设定宽度

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    // 仍然会按照实际的宽度输出
    cout << setw(5) << 123456 << setw(5) << 123456 << endl;

    cout << setw(10) << 123456 << setw(10) << 123456 << endl;

    // 域宽度 只能作用于下一行
    cout << setw(10);
    cout << 123456 << endl;
    
    cout << 123456 << endl;
    return 0;
}

拓展学习

C++如何实现多线程多进程案例:线程池)

文件处理案例:日志系统)

客户端服务器高并发  

Lambda表达式


网站公告

今日签到

点亮在社区的每一天
去签到