C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。使用它们需要包含头文件memory,这些智能指针就包括

  • std::shared_ptr
  • std::unique_ptr
  • std::weak_ptr

    1 std::shared_ptr

    std::shared_ptr可以与其他实例共享指针的所有权,T类型的资源管理的责任,由所有的shared_ptr实例一起接管。
    使用内部的引用计数器来监控当前由多少个管理T类型的实例。当智能指针的最后一个实例被销毁时,T的资源才会被释放。
    image.png

    1.1 创建共享指针

    ```cpp

    include

    include

    include

    include

    using namespace std;

int main() { //创建shared pointer的三种方式 //shared_ptr p1(new string(“nico”)); shared_ptr p1(new string(“nico”), { //这种方式,可以自定义deleter cout << “delete “ << *p << “ in customer deleter” << endl; delete p; }); shared_ptr p2 = make_shared(“jutta”);//推荐使用

  1. //C++20引入,自动按照默认值初始化
  2. shared_ptr<string> p3 = make_shared_for_overwrite<string>();//默认空字符串
  3. //填充vector
  4. vector<shared_ptr<string>> whoMadeCoffee;
  5. whoMadeCoffee.push_back(p2);
  6. whoMadeCoffee.push_back(p2);
  7. whoMadeCoffee.push_back(p1);
  8. whoMadeCoffee.push_back(p2);
  9. whoMadeCoffee.push_back(p1);
  10. //打印vector
  11. for (auto vec : whoMadeCoffee)
  12. {
  13. cout << *vec << " "; //vec是共享指针
  14. }
  15. cout << endl;
  16. //借助指针修改内容
  17. (*p1)[0] = 'N';
  18. p2->replace(0, 1, "J");
  19. //重新打印vector,内容被修改
  20. for (auto vec : whoMadeCoffee)
  21. {
  22. cout << *vec << " ";
  23. }
  24. cout << endl;
  25. //打印指针的引用计数
  26. cout << "p1 count:" << p1.use_count() << endl; //打印3
  27. cout << "p2 count:" << p2.use_count() << endl; //打印4
  28. return 0;

}

  1. <a name="tur0R"></a>
  2. ## 1.2 自定义deleter
  3. 共享指针提供的默认deleter是`**delete**`。<br />我们可以在创建共享指针时,指定自定义的删除器。**注意:**`**make_shared()**`**函数不能指定deleter。**比如:
  4. ```cpp
  5. shared_ptr<string> p1(new string("nico"), [](string *p) { //这种方式,可以自定义deleter
  6. cout << "delete " << *p << " in customer deleter" << endl;
  7. delete p;
  8. });

下面是需要自定义deleter的几个示例,充分说明了自定义删除器的重要性:

  1. //共享智能指针,自定自定义删除器的示例
  2. #include <string>
  3. #include <fstream>
  4. #include <memory>
  5. #include <cstdio>
  6. class FileDeleter
  7. {
  8. private:
  9. std::string filename;
  10. public:
  11. FileDeleter(const std::string &fn) : filename(fn) {}
  12. void operator()(std::ofstream *fp)
  13. {
  14. fp->close(); //关闭文件
  15. std::remove(filename.c_str()); //删除文件
  16. }
  17. };
  18. int main()
  19. {
  20. //创建临时文件,并指定删除器
  21. std::shared_ptr<std::ofstream> fp(new std::ofstream("tmpfile.txt"), FileDeleter("tmpfile.txt"));
  22. //...
  23. }

其实通过RAII的方式,我们可以让上面的代码更加优雅:定义一个类,让其构造函数执行初始化工作,让其析构函数执行清理工作(比如这里的关闭和删除文件)。这样智能指针只负责管理类的实例对象,并可以在默认delete时调用析构函数进行清理

1.3 处理数组指针

由于C语言的规则,我们可能用一个类型指针表示一个数组(指针指向数组的首地址)。如果我们想用shared_prt管理数组的内存就会存在一个问题,因为shared_ptr默认删除器是delete ptr,这里我们需要的是delete[] ptr
所以,管理数组指针时,我们需要自定义一个删除器:

  1. //方法1:自定义删除器
  2. std::shared_ptr<int> p(new int[10], [](int* p){delete[] p;});
  3. //方法2:使用辅助函数替换默认删除器
  4. std::shared_ptr<int> p(new int[10], std::default_delete<int[]>());

2 std::unique_ptr

2.1 使用示例

std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全。

  1. std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
  2. std::unique_ptr<int> pointer2 = pointer; // 非法,独占,不能赋值给其他指针
  3. std::unique_ptr<int> pointer3(pointer); //非法,拷贝构造函数不能使用
  4. //make_unique_for_overwrite是C++20新增,自带默认值初始化
  5. std::unique_ptr<int> pointer = std::make_unique_for_overwrite<int>(); //值默认为0

注意: C++不允许定义**std::unique_ptr<void>**类型,因为unique_ptr没有实现类型删除,不知道类型的话就无法正确释放内存。shared_ptr是可以的。
一个简单的示例:

  1. //独占智能指针
  2. #include <iostream>
  3. #include <string>
  4. #include <thread>
  5. #include <memory>
  6. #include <chrono>
  7. class Person
  8. {
  9. public:
  10. Person() = default;
  11. Person(std::string name, std::uint32_t age)
  12. : _name(std::move(name)), _age(age)
  13. {
  14. }
  15. ~Person()
  16. {
  17. std::cout << "delete the class" << std::endl;
  18. }
  19. void show()
  20. {
  21. std::cout << _name << "\t" << _age << std::endl;
  22. }
  23. private:
  24. std::string _name;
  25. std::uint32_t _age;
  26. };
  27. int main()
  28. {
  29. {
  30. auto ptr = std::make_unique<Person>("barret", 30);
  31. ptr->show();
  32. //开启一个线程,看指针是否会在线程中释放
  33. std::thread([person = std::move(ptr)] {
  34. person->show();//结果是会在子线程结束后释放
  35. }).detach();
  36. }
  37. std::this_thread::sleep_for(std::chrono::seconds(3));//主线程等待3秒
  38. std::cout << "end main function" << std::endl;
  39. return 0;
  40. }

2.2 转移所有权

unique_ptr不能被赋值和拷贝,那有没有方法转移unique_ptr的所有权呢?方法如下:

  • release():放弃所有权,返回原始对象指针
  • std::move():move语义转移所有权

使用示例如下:

  1. std::unique_ptr<ClassA> up(new ClassA());
  2. ClassA* a=up.release();up失去所有权,返回ClassA对象指针
  3. std::unique_ptr<ClassA> up2std::move(up));//up失去所有权,所有权转移给up2

2.3 自定义deleter

当然,也可以像shared_ptr一样自定义删除器,不过稍有不同的是,unique_ptr必须指定删除器的函数签名:

  1. std::unique_ptr<std::string[], void(*)(std::string*)> up{new std::string[10],
  2. [](std::string* p){
  3. delete[] p;
  4. }};
  5. //或者使用std::function
  6. std::unique_ptr<std::string[], std::function<void(std::string*)>> up{new std::string[10],
  7. [](std::string* p){
  8. delete[] p;
  9. }};
  10. //或者使用decltype自动推导删除器的类型
  11. void my_free(std::string* p) { delete[] p; }
  12. std::unique_ptr<std::string[], decltype(&my_free)> up{new std::string[10], my_free};

2.4 处理数组指针

unique_ptr提供了一个偏特化的版本用于处理数组指针,这个版本会默认调用delete[]。需要注意的是,这个版本无法使用指针操作符*->,只能使用数组操作符[],并且无法调用多态。

  1. std::unique_ptr<std::string[]> up(new std::string[10]); // OK
  2. std::cout << *up << std::endl; //编译失败
  3. std::cout << up[0] << std::endl; //OK

3 std::weak_ptr

弱引用指针std::weak_ptr不会引起计数增加,它没有所有权。std::weak_ptr 没有 * 运算符和 -> 运算符,所以不能够对资源进行操作,它的作用是

  • weak_ptr创建需要通过shared_ptr赋值,表示指向此shared_ptr但不增加计数
  • 检查 std::shared_ptr 是否存在, expired()在资源已经被删除释放时返回 true,否则返回 false。比判断**use_count()**效率高
  • 通过lock()函数返回强指针,用于在短暂时间内将弱指针提升为强指针操作数据

shared_ptr无法释放的循环依赖例子:

  1. #include <iostream>
  2. #include <memory>
  3. struct A;
  4. struct B;
  5. struct A{
  6. std::shared_ptr<B> p;
  7. ~A()
  8. {
  9. std::cout<<"A deleted"<<std::endl;
  10. }
  11. };
  12. struct B{
  13. std::shared_ptr<A> p;
  14. ~B()
  15. {
  16. std::cout<<"B deleted"<<std::endl;
  17. }
  18. };
  19. int main()
  20. {
  21. auto a=std::make_shared<A>(); //A计数1
  22. auto b=std::make_shared<B>(); //B计数1
  23. a->p = b; //B计数为2,无法释放
  24. b->p = a; //A计数为2,无法释放
  25. return 0;
  26. }

这里将A和B中p改为weak_ptr指针,则不会增加引用计数,A和B的计数为1,在main之后可以直接释放。

  1. #include <iostream>
  2. #include <memory>
  3. struct A;
  4. struct B;
  5. struct A{
  6. std::weak_ptr<B> p; //改为弱引用
  7. ~A()
  8. {
  9. std::cout<<"A deleted"<<std::endl;
  10. }
  11. };
  12. struct B{
  13. std::weak_ptr<A> p; //改为弱引用
  14. ~B()
  15. {
  16. std::cout<<"B deleted"<<std::endl;
  17. }
  18. };
  19. int main()
  20. {
  21. auto a=std::make_shared<A>(); //A计数1
  22. auto b=std::make_shared<B>(); //B计数1
  23. a->p=b; //B计数为1,b超过作用域可以释放B
  24. b->p=a; //A计数为1,a超过作用域可以释放A
  25. return 0;
  26. }

4 std::enable_shared_from_this

4.1 返回对象共享所有权

一个类继承std::enable_shared_from_this后,可以在方法中通过如下两个方法返回自身的智能指针

  • shared_from_this(): 返回当前对象所有权的shared_ptr指针
  • weak_from_this(): 返回当前对象所有权的weak_ptr指针

注意:不能在构造函数中使用上面两个函数,因为调用构造函数时对象还没有创建完成,无法返回自身指针。

  1. //enable_shared_from_this使用示例
  2. #include <iostream>
  3. #include <string>
  4. #include <vector>
  5. #include <memory>
  6. using namespace std;
  7. class Person : public std::enable_shared_from_this<Person>
  8. {
  9. public:
  10. string name;
  11. shared_ptr<Person> mother;
  12. shared_ptr<Person> father;
  13. //改为弱指针,这样kid就不会增加计数了:kid到parent为强指针,parent到kid为弱指针
  14. vector<weak_ptr<Person>> kids;
  15. Person(const string &n, shared_ptr<Person> m = nullptr,
  16. shared_ptr<Person> f = nullptr)
  17. : name(n), mother(m), father(f)
  18. {
  19. }
  20. ~Person()
  21. {
  22. cout << "delete " << name << endl;
  23. }
  24. void setParentAndTheirKids(shared_ptr<Person> m = nullptr,
  25. shared_ptr<Person> f = nullptr)
  26. {
  27. mother = m;
  28. father = f;
  29. if (m != nullptr)
  30. {
  31. m->kids.push_back(weak_from_this()); //返回当前对象的weak_ptr
  32. }
  33. if (f != nullptr)
  34. {
  35. f->kids.push_back(weak_from_this());
  36. }
  37. }
  38. };
  39. shared_ptr<Person> initFamily(const string &name)
  40. {
  41. shared_ptr<Person> mom = make_shared<Person>(name + "'s mom");
  42. shared_ptr<Person> dad = make_shared<Person>(name + "'s dad");
  43. shared_ptr<Person> kid = make_shared<Person>(name);
  44. kid->setParentAndTheirKids(mom, dad);
  45. return kid;
  46. }
  47. int main()
  48. {
  49. shared_ptr<Person> p = initFamily("barret");
  50. cout << "barret's family exists" << endl;
  51. cout << "- barret is shared " << p.use_count() << " times" << endl;
  52. cout << "- name of 1st kid of barret's mom: "
  53. << p->mother->kids[0].lock()->name << endl;
  54. //正常释放
  55. }

4.2 类内不能使用shared_ptr(this)

可能会有疑问,为什么一定要使用share_fron_this()weak_from_this()来返回智能指针呢,我可以直接在成员函数中return shared_ptr<T>(this)啊,一样和函数返回值能匹配起来。答案是不能使用!!

智能指针使用的时引用计数,就需要针对某个对象指针在一个地方统一计数。所以我更愿意使用所有权(ownership)的概念,不仅仅称呼其为指针。所有权只能在一个地方进行管理,比如你有一万块钱存入了某银行,那这个银行就拥有了这笔钱的所有权,你所有的存取支付都需要通过这个银行来操作(shared_ptr就像是支付宝和微信关联同一个银行卡)。你无法通过其他银行处理这笔钱,其他银行没有这笔钱的所有权。
return shared_ptr<T>(this)会打破统一所有权,它会返回一个智能指针,但是带着新的所有权,而不是和已经存在的其他智能指针共享同一个所有权。这就出现了两个银行同时宣布拥有某一笔钱所有权的问题。引用计数这时候就失效了,因为它压根不知道在另一个地方还有一个堆同一对象的所有权管理。

看下面例子:ptr1和ptr2都认为自己独立拥有Foo对象的所有权,函数结束会导致对象释放两遍。

  1. //演示类对象如何返回自身共享所有权
  2. //以及return shared_ptr<T>(this)导致引用计数失效的问题
  3. #include <iostream>
  4. #include <memory>
  5. using namespace std;
  6. class Foo : public enable_shared_from_this<Foo>
  7. {
  8. public:
  9. ~Foo()
  10. {
  11. cout << "Foo is deleted" << endl;
  12. }
  13. shared_ptr<Foo> getPointer()
  14. {
  15. // return shared_from_this(); //返回对象所有权的共享指针
  16. return shared_ptr<Foo>(this);//返回共享指针,但跟其他指针不共享所有权
  17. }
  18. };
  19. int main()
  20. {
  21. //创建两个智能指针
  22. auto ptr1{make_shared<Foo>()};
  23. auto ptr2{ptr1->getPointer()};
  24. //shared_from_this,所有权引用计数是2
  25. //shared_ptr<Foo>(this),所有权引用计数是1,导致内存重复释放
  26. cout << ptr1.use_count() << endl;
  27. cout << ptr2.use_count() << endl;
  28. }

5 智能指针如何与vector配合??

记住两个原则:

  • C++的Vector 保存原始指针,Vector 析构的时候是不会释放指针指向的堆对象,
  • 如果用Vector保存智能指针,可以释放智能指针所指向的对象的

示例如下:结果是四次create,两次delete。

  1. #include <iostream>
  2. #include <vector>
  3. #include <memory>
  4. using namespace std;
  5. class Human
  6. {
  7. public:
  8. Human() { cout << "create Human" << endl; }
  9. ~Human() { cout << "delete Human" << endl; }
  10. };
  11. int main()
  12. {
  13. vector<Human*> list1 = { new Human(), new Human() };
  14. vector<shared_ptr<Human>> list2 = { make_shared<Human>() ,make_shared<Human>() };
  15. return 0;
  16. }

6 智能指针类型转换

和基本类型、原始指针一样,智能指针也能进行类型转换。从一个类型的指针转换为另一个类型的指针,其cast函数和普通类型的cast函数很相似:

  • std::static_pointer_cast
  • std::dynamic_pointer_cast
  • std::const_pointer_cast
  • std::reinterpret_pointer_cast

上述函数可以直接转换只能指针。如果想用不带_pointer_系列的cast函数,需要提取出原始指针再转换,不能直接转换智能指针。示例:

  1. #include <iostream>
  2. #include <memory>
  3. struct BaseClass {};
  4. struct DerivedClass : BaseClass
  5. {
  6. void f() const
  7. {
  8. std::cout << "Hello World!\n";
  9. }
  10. ~DerivedClass() { // 注意:它不是虚的
  11. std::cout << "~DerivedClass\n";
  12. }
  13. };
  14. int main()
  15. {
  16. std::shared_ptr<BaseClass> ptr_to_base(std::make_shared<DerivedClass>());
  17. //ptr_to_base->f(); // 错误不会编译: BaseClass 无名为 'f' 的成员
  18. std::static_pointer_cast<DerivedClass>(ptr_to_base)->f(); // OK
  19. // (构造临时 shared_ptr ,然后调用 operator-> )
  20. static_cast<DerivedClass*>(ptr_to_base.get())->f(); // 亦 OK
  21. // (直接转型,不构造临时 shared_ptr )
  22. }