C++智能指针原理及用法总结

部分内容摘自 链接

原理
智能指针是一个类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄 漏。动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源
常用的智能指针

(1)shared_ptr

实现原理:采用引用计数器的方法,允许多个智能指针指向同一个对象,每当多一个指针指向该对象 时,指向该对象的所有智能指针内部的引用计数加1,每当减少一个智能指针指向对象时,引用计数会 减1,当计数为0的时候会自动的释放动态分配的资源。
智能指针将一个计数器与类指向的对象相关联,引用计数器跟踪共有多少个类对象共享同一指针 每次创建类的新对象时,初始化指针并将引用计数置为1
当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数
对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0, 则删除对象),并增加右操作数所指对象的引用计数
调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)

使用方法:

1. 创建shared_ptr对象

shared_ptr可以通过构造函数或make_shared函数创建,如下所示:

//使用构造函数创建shared_ptr
shared_ptr<int> p1(new int(10));
shared_ptr<int> p2(p1);

//使用make_shared函数创建shared_ptr
shared_ptr<int> p3 = make_shared<int>(20);
shared_ptr<int> p4 = p3;

2. 获取指向的对象

可以使用*或->运算符获取指向的对象,如下所示:

int a = *p1; //获取指向的对象,a的值为10
p2->someFunction(); //调用指向的对象的成员函数

3. 释放资源

当最后一个指向资源的shared_ptr离开作用域时,会自动释放资源,无需手动释放。也可以使用reset函数手动释放资源,如下所示:

p1.reset(); //手动释放资源

4. 获取引用计数

可以使用use_count函数获取当前资源的引用计数,如下所示:

int count = p2.use_count(); //获取引用计数,count的值为2

代码示例:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass constructed" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destroyed" << std::endl;
    }
    void sayHello() {
        std::cout << "Hello, World!" << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClass> ptr1(new MyClass);
    std::shared_ptr<MyClass> ptr2(ptr1);

    std::cout << "ptr1.use_count() = " << ptr1.use_count() << std::endl;
    std::cout << "ptr2.use_count() = " << ptr2.use_count() << std::endl;

    ptr1->sayHello();
    ptr2->sayHello();

    return 0;
}

(2)unique_ptr

unique_ptr采用的是独享所有权语义,一个非空的unique_ptr总是拥有它所指向的资源。转移一个unique_ptr将会把所有权全部从源指针转移给目标指针,源指针被置空;所以unique_ptr不支持普通的 拷贝和赋值操作,不能用在STL标准容器中;局部变量的返回值除外(因为编译器知道要返回的对象将 要被销毁);如果你拷贝一个unique_ptr,那么拷贝结束后,这两个unique_ptr都会指向相同的资源, 造成在结束时对同一内存指针多次释放而导致程序崩溃。

unique_ptr的使用方法如下:

1. 创建unique_ptr对象

可以通过new关键字创建一个unique_ptr对象,如下所示:

std::unique_ptr<int> ptr(new int(10));

这行代码创建了一个unique_ptr对象,指向一个动态分配的int类型内存,并初始化为10。

2. 获取指向的内存地址

可以使用get()方法获取unique_ptr指向的内存地址,如下所示:

int* p = ptr.get();

这行代码获取了unique_ptr指向的内存地址,并将其赋值给指针p。

3. 释放指向的内存

可以使用reset()方法释放unique_ptr指向的内存,如下所示:

ptr.reset();

这行代码释放了unique_ptr指向的内存。

4. 转移所有权

可以使用std::move()函数将unique_ptr的所有权转移给另一个unique_ptr对象,如下所示:

std::unique_ptr<int> ptr1 = std::move(ptr);

这行代码将ptr的所有权转移给ptr1,ptr不再指向任何内存。

  1. 自定义删除器

可以使用自定义删除器来释放unique_ptr指向的内存,如下所示:

std::unique_ptr<int, void(*)(int*)> ptr(new int(10), [](int* p){ delete p; });

这行代码创建了一个unique_ptr对象,指向一个动态分配的int类型内存,并使用自定义删除器来释放内存。

下面是一个完整的示例代码:

#include <iostream>
#include <memory>

int main()
{
    // 创建unique_ptr对象
    std::unique_ptr<int> ptr(new int(10));

    // 获取指向的内存地址
    int* p = ptr.get();
    std::cout << "p = " << p << std::endl;

    // 释放指向的内存
    ptr.reset();

    // 创建另一个unique_ptr对象,并转移所有权
    std::unique_ptr<int> ptr1 = std::move(ptr);
    std::cout << "ptr1 = " << *ptr1 << std::endl;

    // 使用自定义删除器释放内存
    std::unique_ptr<int, void(*)(int*)> ptr2(new int(20), [](int* p){ delete p; });
    std::cout << "ptr2 = " << *ptr2 << std::endl;

    return 0;
}

(3)weak_ptr

weak_ptr:弱引用。 引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。需要使用weak_ptr打破环形引用。weak_ptr是一个弱引用,它是为了配合shared_ptr 而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是 说,它只引用,不计数。如果一块内存被shared_ptr和weak_ptr同时引用,当所有shared_ptr析构了之 后,不管还有没有weak_ptr引用该内存,内存也会被释放。所以weak_ptr不保证它指向的内存一定是 有效的,在使用之前使用函数lock()检查weak_ptr是否为空指针。

下面是几个使用weak_ptr的示例:

1. 在类中使用weak_ptr作为成员变量

class A {
public:
    A() {
        cout << "A constructor called." << endl;
    }
    ~A() {
        cout << "A destructor called." << endl;
    }
};

class B {
public:
    B() {
        cout << "B constructor called." << endl;
    }
    ~B() {
        cout << "B destructor called." << endl;
    }
private:
    weak_ptr<A> a_ptr_;
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    shared_ptr<B> b = make_shared<B>();
    b->a_ptr_ = a;
    return 0;
}

在这个例子中,类B中有一个weak_ptr成员变量a_ptr_,它指向类A的对象。在main函数中,我们创建了一个A对象和一个B对象,并将B对象的a_ptr_指向A对象。由于a_ptr_是一个weak_ptr,它不会增加A对象的引用计数。当A对象的引用计数变为0时,它将被销毁。

2. 使用weak_ptr判断对象是否存在

class A {
public:
    A() {
        cout << "A constructor called." << endl;
    }
    ~A() {
        cout << "A destructor called." << endl;
    }
};

int main() {
    shared_ptr<A> a = make_shared<A>();
    weak_ptr<A> weak_a = a;
    if (auto shared_a = weak_a.lock()) {
        cout << "A object exists." << endl;
    } else {
        cout << "A object does not exist." << endl;
    }
    a.reset();
    if (auto shared_a = weak_a.lock()) {
        cout << "A object exists." << endl;
    } else {
        cout << "A object does not exist." << endl;
    }
    return 0;
}

在这个例子中,我们创建了一个A对象和一个weak_ptr指向它。使用lock()方法可以检查A对象是否存在。如果A对象存在,lock()方法将返回一个shared_ptr,否则返回一个空指针。在main函数中,我们首先检查A对象是否存在,然后将其引用计数减少到0,并再次检查它是否存在。

3. 使用weak_ptr实现循环引用

class B;

class A {
public:
    A(shared_ptr<B> b) : b_ptr_(b) {
        cout << "A constructor called." << endl;
    }
    ~A() {
        cout << "A destructor called." << endl;
    }
private:
    shared_ptr<B> b_ptr_;
};

class B {
public:
    B(shared_ptr<A> a) : a_ptr_(a) {
        cout << "B constructor called." << endl;
    }
    ~B() {
        cout << "B destructor called." << endl;
    }
private:
    weak_ptr<A> a_ptr_;
};

int main() {
    shared_ptr<A> a = make_shared<A>(nullptr);
    shared_ptr<B> b = make_shared<B>(a);
    a->b_ptr_ = b;
    return 0;
}

在这个例子中,类A和类B互相引用,会导致循环引用的问题。为了解决这个问题,我们可以将类B中的a_ptr_定义为weak_ptr。这样,当A对象的引用计数变为0时,它将被销毁,B对象也会被销毁。

以上三个示例展示了在C++中使用weak_ptr的几种常见用法。注意,在使用weak_ptr时,需要注意避免空指针的问题。

(4)auto_ptr

主要是为了解决“有异常抛出时发生内存泄漏”的问题 。因为发生异常而无法正常释放内存。
auto_ptr有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr则无拷贝语 义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()进行转移。
auto_ptr不支持拷贝和赋值操作,不能用在STL标准容器中。STL容器中的元素经常要支持拷贝、赋值操 作,在这过程中auto_ptr会传递所有权,所以不能在STL中使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_WAWA鱼_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值