C++11:智能指针

简介: C++11:智能指针

背景:

  • 内存泄漏,自动释放。栈上创建的智能指针指向堆上创建的对象。
  • 共享所有权指针的传播和释放

方法:

p.get() // 获取原始指针的值
 /* 问题:不能保存get()的返回值 -> 空悬指针;也不能delet get()的返回值,delete两次 */
 p.reset(...) // 重置指针
 /* 无参数 -> p指针置空;有参数 -> p指向...的对象,并释放(delete)p原来的空间 
 注:对share_ptr来说,若p指针不是唯一指向该对象的指针,delte操作只减少其引用计数 */

1. auto_ptr

已经弃用,语法形式上表达的是值语义,但底层实现时已经发生了所有权的转移

2. unique_ptr

  • 独享所有权的智能指针
unique_ptr<Point> up(new Point);
 auto up(std::make_unique<Point>()); // C++14后才有
 /* 注:new 重复了被创建对象的键入,但是 make_unique 函数则没有。工程中应当避免代码重复造成引起编译次数增加。 */
  • 不能表达对象语义,即不能进行复制控制(拷贝和赋值)
unique_ptr<Point> up2(up1); // error
up3 = up;                  // error
  • 具有移动语义。转移后,所有权从源指针转移到目标指针,源指针置空。
// 1、unique_ptr 具有移动语义的函数,转换成右值后托管的资源变成了空指针
 unique_ptr<Point> up(new Point(1, 2));
 up2 = std::move(up); // up2->up,up->nullptr
 // 2、unique_ptr 作为容器元素
 unique_ptr<Point> up(new Point(1, 2)); // 左值
 vector<unique_ptr<Point>> points; // 容器中存放的是智能指针
 // points.push_back(up); // error,传递左值,调用拷贝构造函数,表达对象语义错误
 points.push_back(std::move(up)); // 正确,右值,调用移动赋值运算符函数
  • 指定删除器(需要指定删除器的类型),回收资源
// unique_ptr可以指向一个数组,shared_ptr C++17后支持
 unique_ptr<int []> ptr(new int[10]); // C++11支持
 shared_ptr<int []> ptr2(new int[10]); // C++17支持,
 // unique_ptr需要确定删除器的类型
 // unique_ptr<int> ptr(new int(1), [](int *p){delete p;}); //error
 unique_ptr<int, void(*)(int*)> ptr(new int(1), [](int *p){delete p;});

简易源码

template <typename T>
class unique_ptr {
public:
    // 构造函数
    explicit unique_ptr(T* ptr = nullptr) : data(ptr) {}
    // 禁止拷贝构造和赋值操作符
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
    // 移动构造函数
    unique_ptr(unique_ptr&& other) noexcept : data(other.data) {
        other.data = nullptr;
    }
    // 移动赋值操作符
    unique_ptr& operator=(unique_ptr&& other) noexcept {
        if (this != &other) {
            reset(other.data);
            other.data = nullptr;
        }
        return *this;
    }
    // 析构函数
    ~unique_ptr() {
        reset();
    }
    // 获取原始指针
    T* get() const {
        return data;
    }
     // 重载解引用操作符 *
     T& operator*() const {
         return *data;
     }
     // 重载箭头操作符 ->
     T* operator->() const {
         return data;
     }
     // 显式释放资源,并重置指针为nullptr
     void reset(T* ptr = nullptr) noexcept {
         if (data != ptr) {
             delete data;
             data = ptr;
         }
     }
private:
   T* data;  // 存储指向对象的原始指针
};


3. shared_ptr

share_ptr 原理

shared_ptr 包含两个指针,一个指向堆上创建的对象的裸指针,另一个指向控制块。

share_ptr

share_ptr 特点

  • 共享所有权
// 初始化
 shared_ptr<int> sp = make_shared<int>(100); // 推荐
 shared_ptr<int> p1(new int(1));
  • 强引用
  • 可以进行复制控制,通过引用计数来完成。当复制控制时,引用计数+1。当被销毁时,先将引用计数减1;再去判断引用计数是否为0;如果为0, 才真正释放资源。通过 use_count 获取引用计数。
  • 可以进行转移操作
  • 指定删除器,回收资源
std::shared_ptr<int> p3(new int[10], [](int *p) { delete [] p;});

share_ptr 常用方法

s.get():// 返回shared_ptr中保存的裸指针;
 s.reset(...):// 重置shared_ptr
 s.use_count() // 返回shared_ptr的强引用计数;
 s.unique() :// use_count()为1,返回true,否则返回false。

share_ptr 问题

  • 不能用一个原始指针初始化多个 shared_ptr
int *ptr = new int; 
 shared_ptr<int> p1(ptr); 
 shared_ptr<int> p2(ptr); // 逻辑错误,其他share_ptr不知道共享对象
  • 不要在函数实参中创建 shared_ptr,函数创建失败,但内存已分配
  • 通过 shared_from_this() 返回 this 指针
    不要直接用 shared_ptr 返回 this 指针, this 指针本质上是一个裸指针,可能会重复析构。正确返回 this 的 shared_ptr 的方法是:让目标类通过继承 std::enable_shared_from_this 类,然后使用基类的成员函数 shared_from_this() 来返回 this 的 shared_ptr
// 1、让目标类通过继承 std::enable_shared_from_this 类
class A: public std::enable_shared_from_this<A> {
public: 
    shared_ptr<A>GetSelf() { 
        // 2、使用基类成员函数 shared_from_this() 来返回 this 的 shared_ptr
        return shared_from_this(); 
    }
    ~A() { 
        cout << "Destructor A" << endl; 
    } 
};
int main() { 
    shared_ptr<A> sp1(new A); 
    shared_ptr<A> sp2 = sp1->GetSelf(); // ok,若直接使用this指析构两次
    return 0; 
}
// 原理:学完 weak_ptr 再来
/* 
原因:std::enable_shared_from_this 类中有一个weak_ptr,用来观察this智能指针。调用shared_from_this() 方法会调用内部这个weak_ptr的lock()方法返回观察的shared_ptr,其引用计数并不发生改变,不会重复析构。
注:获取自身智能指针的函数仅在 shared_ptr 的构造函数被调用之后才能使用,因为
enable_shared_from_this 内部的 weak_ptr 只有通过 shared_ptr 才能构造。
*/
  • 循环引用问题:两个 shared_ptr 互指,导致引用计数增加,不能靠对象的销毁使得引用计数变为0,从而导致内存泄漏。解决方法:将两个类的任一成员变量改为weakptr
class Child;
class Parent {
public: 
  ~Parent() {
    cout << "~Parent()" << endl;
  }
  shared_ptr<Child> pChild; // error,正确应为:weak_ptr<Child> pChild;
};
class Child {
public: 
  ~Child() {
    cout << "~Child()" << endl;
  }
  shared_ptr<Parent> pParent; // error,正确应为:weak_ptr<Parent> pParent;
};
int main(void)
{
  {
    shared_ptr<Child> child(new Child);
    shared_ptr<Parent> parent(new Parent);
    child->pParent = parent;
    parent->pChild = child;
    cout << "child的引用计数:" << child.use_count() << endl; //2
    cout << "parent的引用计数:" << parent.use_count() << endl; //2
  }
  // 问题:循环引用:退出了作用域,但是没有调用析构
  cout << "main leave" << endl; 
  return 0;
}

简易源码

template <typename T>
class shared_ptr {
public:
    explicit shared_ptr(T* ptr = nullptr) : 
        data(ptr), ref_count(new size_t(1)) {}
    ~shared_ptr() {
        if (--(*ref_count) == 0) {
            delete data;
            delete ref_count;
        }
    }
    shared_ptr(const shared_ptr<T>& other) :
        data(other.data), ref_count(other.ref_count) {
        ++(*ref_count);
    }
    shared_ptr<T>& operator=(const shared_ptr<T>& other) {
        if (this != &other) {
            if (--(*ref_count) == 0) {
                delete data;
                delete ref_count;
            }
            data = other.data;
            ref_count = other.ref_count;
            ++(*ref_count);
        }
        return *this;
    }
    T& operator*() const { return *data; }
    T* operator->() const { return data; }
private:
    T* data;              // 指向所管理的对象
    size_t* ref_count;    // 引用计数
};


4. weak_ptr

弱引用指针,了解决 shared_ptr 循环引用的问题而提出来的。不控制对象生命周期,引用计数保持不变。

weak_ptr 特点

  • weak_ptr 不能直接托管资源,只能通过 shared_ptr 复制过来
  • 引用计数保持不变。
  • 特殊:weak_ptr 没有提供访问资源 的方法,没有实现 opeartor*/->
  • 要访问资源,必须要先通过 lock 函数提升为 shared_ptr 才可以访问。
  • weak_ptr 知道所托管的资源是否还存活

weak_ptr 方法

use_count() // 获取当前观察资源的引用计数
 expired() // 判断所观察资源是否已经释放
 lock() // 获取监视的 shared_ptr

weak_ptr 使用

weak_ptr<int> wp; 
 { 
     shared_ptr<int> sp(new int(1)); //sp.use_count()==1 
     wp = sp; // wp不会改变引用计数,sp.use_count()==1
     // weak_ptr不提供访问资源的方法。
     // 如果要访问资源,只能调用lock方法将weak_ptr提升成shared_ptr
     shared_ptr<int> sp2 = wp.lock(); 
 }
 // weak_ptr在使用前需要expired()函数判断资源是否存在
 if(wp.expired()) { 
     cout << "shared_ptr is destroy" << endl; 
 } 
 else { 
     cout << "shared_ptr no destroy" << endl; 
 }

5、删除器的使用

智能指针默认使用 delete 来释放空间。若采用 malloc 申请的空间或是用 fopen 打开的文件,智能指针无法处理,因此我们需要为智能指针定制删除器。

自定义智能指针的方式有两种,函数指针与仿函数(函数对象)

struct FILECloser { 
     void operator()(FILE *fp) { 
         if(fp) { 
             fclose(fp); 
             cout << "fclose(fp)" << endl; 
         } 
     } 
 };
 shared_ptr<FILE> up(fopen("wuhan1.txt", "a+"), FILECloser());
相关文章
|
7天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
25 4
|
23天前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
1月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
37 1
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
29 2
|
1月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
1月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
|
2月前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
37 3
|
1月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。
|
2月前
|
C++
C++(九)this指针
`this`指针是系统在创建对象时默认生成的,用于指向当前对象,便于使用。其特性包括:指向当前对象,适用于所有成员函数但不适用于初始化列表;作为隐含参数传递,不影响对象大小;类型为`ClassName* const`,指向不可变。`this`的作用在于避免参数与成员变量重名,并支持多重串联调用。例如,在`Stu`类中,通过`this-&gt;name`和`this-&gt;age`明确区分局部变量与成员变量,同时支持链式调用如`s.growUp().growUp()`。