Skip to content

Latest commit

 

History

History
184 lines (105 loc) · 8.14 KB

智能指针.md

File metadata and controls

184 lines (105 loc) · 8.14 KB

auto_ptr和unique_ptr

auto_ptr:

同一个对象只可被一个auto_ptr拥有

  • 复制的时候可能会出现问题,所以auto_ptr的复制是转移对象拥有权
  • 被转移的auto_ptr又有可能在析构的时候删除根本不存在的对象,即访问无效内存,所以要在析构函数中加一层是否还拥有原来指向对象的指针的判断
  • 在复制和赋值的时候改变了参数的内容,换句话说,如果auto_ptr被复制,它们会被修改。传统的复制和赋值通常需要用const,但是这里不行

unique_ptr:

unique_ptr也使用独占所有权的方式,但是禁止了拷贝构造和拷贝赋值操作,并且可以自定义删除器。

结论:

由于 auto_ptr 允许拷贝构造和拷贝赋值操作,这导致在某些情况下,所有权的转移并不直观,容易引发潜在的资源泄漏或访问无效内存的问题。此外,auto_ptr 不支持自定义删除器,限制了其灵活性。

shared_ptr

std::shared_ptr 中,实际的值是存储在动态分配的堆内存上的。

std::shared_ptr 通过使用引用计数技术来管理共享资源的所有权。它包含两个部分:控制块(control block)和指向堆内存的指针。

控制块是一个数据结构,它存储了引用计数和其他与 std::shared_ptr 相关的信息。它通常会在堆上分配,并由 std::shared_ptr 对象共享。控制块中的引用计数跟踪有多少个 std::shared_ptr 对象共享同一个资源。

当有新的 std::shared_ptr 对象指向相同的资源时,它们会共享同一个控制块和指向堆内存的指针。引用计数会相应增加,确保资源只在最后一个 std::shared_ptr 对象销毁时释放。

当引用计数降为零时,即所有的 std::shared_ptr 对象都销毁时,控制块会负责释放堆内存和自身的内存。

实际的值是存储在指向堆内存的指针所指向的位置。而 std::shared_ptr 对象本身只是包装了控制块和指向堆内存的指针,用于管理资源的共享和释放。

shared_ptr数据结构

_Uses_Weaks_vfptrshared_ptr 内部实现的一些成员变量。

  • _Uses 是一个指向引用计数控制块的指针。该控制块存储了引用计数和其他与 shared_ptr 相关的信息。每个 shared_ptr 对象都共享同一个 _Uses 指针,通过该指针可以更新引用计数。
  • _Weaks 是一个指向弱引用计数控制块的指针。弱引用是一种不增加引用计数的指针,用于监测对象是否已被销毁。_Weaks 存储了与弱引用相关的信息,包括弱引用计数。当弱引用计数为零时,表示没有任何 weak_ptr 指向对象,可以安全地销毁对象。
  • _vfptr 是虚函数指针,用于实现 shared_ptr 的多态性。shared_ptr 是一个模板类,因此 _vfptr 指向了一个虚函数表,包含了 shared_ptr 的虚函数。通过 _vfptrshared_ptr 可以在运行时根据实际对象类型调用适当的函数。

这些成员变量是 shared_ptr 内部实现的细节,对于一般的使用场景来说,不需要直接操作这些成员变量。相反,应该使用 shared_ptr 提供的公共接口来管理资源和访问对象。这样可以确保正确的引用计数和资源管理,而无需关心内部实现细节。

image-20230925223559115

在《more effective c++》中

  • 目标是建造一个引用计数的string类

  • 以RCObject管理refCount,让StringValue继承RCObject,同时里面用char*指针存储数据,实现表现字符串实值。

  • 为了实现多个等值对象共享同一个实值,再创建智能指针RCPtr,指向StringValue,让string通过管理智能指针的方法实现引用计数的加减。

weak_ptr

  1. weak_ptr不会增加shared_ptr的引用计数,可以破解循环引用
  2. weak_ptr可以用它的成员函数lock安全的得到shared_ptr,以判断shared_ptr是否还有效

下面是一个示例程序,演示如何使用 std::weak_ptrstd::shared_ptr 相互配合,实现弱引用和共享引用的功能:

#include <iostream>
#include <memory>

struct MyObject {
    int value;

    MyObject(int val) : value(val) {
        std::cout << "Constructing MyObject with value: " << value << std::endl;
    }

    ~MyObject() {
        std::cout << "Destructing MyObject with value: " << value << std::endl;
    }
};

int main() {
    std::shared_ptr<MyObject> sharedPtr = std::make_shared<MyObject>(42);

    std::weak_ptr<MyObject> weakPtr(sharedPtr);

    // 使用 shared_ptr 访问对象
    if (std::shared_ptr<MyObject> shared = weakPtr.lock()) {
        std::cout << "Weak pointer value: " << shared->value << std::endl;
    }
    else {
        std::cout << "Object is no longer available." << std::endl;
    }

    // 释放 shared_ptr
    sharedPtr.reset();

    // 使用 weak_ptr 访问对象
    if (std::shared_ptr<MyObject> shared = weakPtr.lock()) {
        std::cout << "Weak pointer value: " << shared->value << std::endl;
    }
    else {
        std::cout << "Object is no longer available." << std::endl;
    }

    return 0;
}

在上述示例中,我们首先创建了一个 std::shared_ptr 对象 sharedPtr,它指向一个 MyObject 对象。

然后,我们使用 std::weak_ptr 创建了一个弱引用 weakPtr,它与 sharedPtr 共享相同的资源。

我们使用 weakPtrlock() 成员函数来尝试获取一个有效的 std::shared_ptr 对象,以访问所指向的对象。如果返回的 shared_ptr 对象不为空,则表示对象仍然存在,我们可以使用该 shared_ptr 访问对象的成员。

接着,我们通过 sharedPtr.reset()sharedPtr 释放,使其引用计数降为零,从而销毁 MyObject 对象。

最后,我们再次使用 weakPtrlock() 成员函数来尝试获取 shared_ptr,但由于对象已经被销毁,返回的 shared_ptr 对象为空。

通过上述示例,我们展示了如何使用 std::weak_ptrstd::shared_ptr 相互配合,实现弱引用的功能,并在需要时安全地访问共享对象。

内存泄露

在 C++ 中,内存泄漏是一个常见问题。内存泄漏通常发生在一段内存被分配(比如通过 new 操作符)但永远不会被释放(比如通过 delete 操作符)。在某些情况下,尤其是在涉及循环引用的情况下,内存泄漏就可能发生。

先来看看什么是循环引用:两个或多个对象相互持有对方的引用,形成一个闭环。即使外部没有任何对这些对象的引用,它们也不能被正确的析构和删除,因为它们彼此间保持着对方的引用,形成一个闭环。例如:

class Object {
public:
    std::shared_ptr<Object> partner;
};

void createCycle() {
    auto obj1 = std::make_shared<Object>();
    auto obj2 = std::make_shared<Object>();

    obj1->partner = obj2;
    obj2->partner = obj1;

}

在 createCycle 函数中,obj1 和 obj2 形成了一个循环引用。即使 createCycle 函数执行完毕后,obj1 和 obj2 都离开了作用域,obj1 和 obj2的引用计数还仍为1,所以他们的析构函数也不会被调用,因为它们仍然被对方引用着。结果就是这两个对象永远不会被释放,导致内存泄漏。

弱引用

这时,我们可以通过弱引用来打破这种循环引用,避免内存泄漏。弱引用不会增加对象的引用计数,所以不会阻止对象的析构和删除。下面是使用弱引用避免循环引用的例子:

class Object {
public:
    std::weak_ptr<Object> partner;
};

void createCycle() {
    auto obj1 = std::make_shared<Object>();
    auto obj2 = std::make_shared<Object>();

    obj1->partner = obj2;
    obj2->partner = obj1;

}

在这个例子中,我们用 std::weak_ptr 代替了 std::shared_ptr。现在,即使 obj1 和 obj2 互相引用,它们也会在 createCycle 函数结束时被正确地析构和删除,从而避免了内存泄漏。

写时才复制

和其他对象共享一份实值,直到我们必须对自己所拥有的那一份实值进行写动作